3.2. 우선순위와 결합성
연산자에는 우선순위 (precedence) 와 결합성 (associativity) 이라는 두 가지 속성이 있습니다. 하나의 표현식 안에 여러 연산자가 함께 등장할 때, 이 두 속성이 어떤 모양의 트리가 만들어질지를 결정합니다.
3.2.1. 우선순위
a + b * c → a + (b * c) // * 가 + 보다 강함
a * b + c → (a * b) + c // 마찬가지
a < b + c → a < (b + c) // + 가 < 보다 강함
우선순위 값은 정수로 표현하며, 더 큰 값을 가진 연산자가 먼저 묶이게 됩니다.
3.2.2. 결합성
a - b - c → (a - b) - c // 좌결합
a ?? b ?? c → a ?? (b ?? c) // 우결합 (예시)
a < b < c → ? // 비결합 (체이닝 금지)
결합성은 다음 세 가지 중 하나로 정해집니다.
| 표기 | 의미 |
|---|---|
left | 좌측부터 묶임. a OP b OP c = (a OP b) OP c |
right | 우측부터 묶임. a OP b OP c = a OP (b OP c) |
none | 체이닝 금지. a OP b OP c 는 컴파일 에러 |
3.2.3. 우선순위와 결합성 표
모든 내장 연산자 토큰의 우선순위와 결합성은 언어가 고정하며, 아래 표가 그 단일 권위입니다. 표준 라이브러리의 #[infix] / #[prefix] / #[postfix] 는 토큰과 트레잇 메서드를 연결할 뿐, 우선순위나 결합성을 정하거나 바꾸지 않습니다.
우선순위 등급
등급 연산자 결합
13 후위: 멤버 접근 (.), ?., 메서드 호출, 인덱싱, ? 좌
12 전위 단항: - ! ~ √ await -
11 as is (후위 형 변환 / 형 검사) 좌
10 << >> 좌
9 ∘ (함수 합성) 좌
8 * / % %% × ⋅ ÷ ∩ 좌
7 + - ∪ 좌
6 & ^ 좌
5 | 좌
4 == != < > <= >= ≡ ≠ ≤ ≥ ∈ ∉ ⊂ ⊆ 비결합
3 && 좌
2 || 좌
1 ?? (null 합체) 우
0 .. ..= (범위) 비결합
-1 = += -= *= /= %= %%= &= |= ^= <<= >>= (대입) 우
표의 Unicode 연산자 (× ⋅ ÷ ∩ ∪ ∘ √ ≡ ≠ ≤ ≥ ∈ ∉ ⊂ ⊆) 는 1.10.3 의 고정 토큰입니다. 3.2 표에 없는 확장 토큰 (1.10.4) 은 새 단계를 만들지 않고, 등록할 때 like = "+" 처럼 지정한 기존 토큰의 단계와 결합성을 그대로 따릅니다 (6.7.2).
중요
?? 와 .. 의 상대 우선순위: ?? (prec=1) 는 범위 연산자 .. / ..= (prec=0) 보다 강하게 묶입니다. 따라서 a ?? 0..3 은 (a ?? 0)..3 으로 묶이며, 범위 연산자는 대입을 제외한 모든 이항 연산자보다 약합니다 (a || b..c 는 (a || b)..c). 두 연산자는 트레잇으로 디스패치되지 않고 언어가 직접 처리합니다 - ?? 는 합 타입 위의 언어 수준 desugar (3.8.4) 이고, 범위는 언어가 여섯 형태를 별개의 표현식으로 인식하는 구문 (3.7) 이기 때문입니다. 범위 연산자는 비결합이므로 a..b..c 는 컴파일 에러입니다.
중요
try 와 catch / finally 의 결합: 에러 제어 흐름 의 try / try? / try! 와 catch / finally 절도 트레잇 연결이 없는 언어 구문입니다. try 는 전위 형태이지만 표의 전위 단항 연산자 (prec=12) 와 달리 오른쪽 식 전체를 덮습니다. 그 피연산자는 catch / finally 절, 문장 종결자 ;, 닫는 괄호, 콤마 앞까지 이어지므로 try a + b() 는 try (a + b()) 입니다. catch / finally 절은 대입 (prec=-1) 보다 강하고 그 밖의 모든 연산자보다 약하게 묶여, 왼쪽의 식 전체에 붙습니다. 따라서 try parse(s) catch _ { 0 } 은 (try parse(s)) catch _ { 0 } 이고, x = try f() catch _ { 0 } 은 x = ((try f()) catch _ { 0 }) 입니다.
중요
await: await 는 - 나 ! 처럼 prec=12 의 전위 단항 연산자이므로, 피연산자는 단항 식입니다: await f() == x 는 (await f()) == x 입니다 (4.12.2).
중요
as 와 is: 두 후위 연산자는 트레잇 연결이 없는 언어 구문이며, 같은 등급 (prec=11) 으로 묶입니다. 전위 단항보다 약하고 산술보다 강하므로 -x as i64 는 (-x) as i64, a + b is i32 는 a + (b is i32) 입니다. e is T 의 결과는 bool 입니다 (2.2.5).
참고
점-접두 (broadcast) 연산자의 우선순위: .+, .*, .&&, .%% 같은 점-접두 형태의 우선순위와 결합성은 점 없는 형태와 동일합니다. 별도의 행으로 표기하지는 않으나, 모든 점-접두 토큰은 대응하는 일반 토큰의 등급을 그대로 따릅니다. 예: .+ 는 + 와 같은 prec=7 left, .&& 는 && 와 같은 prec=3 left.
참고
시프트 연산이 산술 연산보다 강하게 묶인다는 점이 C 와 다른 부분입니다. 따라서 1 + 2 << 3 은 1 + (2 << 3) = 17 으로 묶이게 됩니다. 직관에서 벗어난다고 느껴지신다면 괄호로 명시해 주시는 편이 적합합니다.
같은 토큰의 다중 위치
같은 토큰이 전위, 이항, 후위로 모두 등장할 수 있는 경우 (예를 들어 -) 에는 각 위치별로 별개의 우선순위가 정해집니다 (보통은 전위가 이항보다 강합니다).
-a + b → (-a) + b // 전위 - 가 + 보다 강함
3.2.4. 충돌
우선순위는 표가 고정하므로 등록끼리 우선순위가 충돌하는 일은 없습니다. 같은 (토큰, 위치) 에 서로 다른 트레잇을 연결하는 것은 6.7.5 에 따라 컴파일 에러입니다.
3.2.5. 비교 체이닝 금지
경고
수학적 직관 (a < b < c 가 a < b 그리고 b < c 를 의미) 에 따른 체이닝은 rok 에서는 허용되지 않습니다.
비교 연산자 (<, >, <=, >=, ==, !=) 는 3.2.3 표에서 모두 비결합입니다.
a < b < c // ERROR: 체이닝 금지
a == b == c // ERROR
원하는 의미가 있다면 다음과 같이 명시적으로 적어 주셔야 합니다.
a < b && b < c
이러한 결정에는 두 가지 근거가 있습니다.
- 비교의 결과는
bool인데, 그bool을 다시 비교한다는 것은 수학적 직관과 어긋나기 때문입니다. - 체이닝을 받아들이려면 별도의 체이닝 규칙이 필요한데, 그 규칙은 다른 연산자와 일관되지 않게 되기 때문입니다.
따라서 명시적으로 && 를 사용하는 방식이 가장 단순하고 일관된 표현이라 할 수 있습니다.
3.2.6. 괄호로 강제 묶기
위의 규칙에 따라 만들어지는 트리가 의도한 모양과 다를 때에는, 괄호를 사용해 강제로 묶어 주실 수 있습니다.
a + b * c → a + (b * c) // 기본 묶임
(a + b) * c → (a + b) * c // 명시적
괄호는 어휘 단계와 구문 단계에서 그룹화 토큰으로 동작하며, 트리의 모양을 직접 제어할 수 있게 합니다.
3.2.6a. 구현 한도
연산자 / 후위 연산의 사슬이 4096 개를 넘거나 중첩이 256 보다 깊으면 에러입니다. 이것은 rok 의 한도입니다 (구현이 정함, 0.4.4).
3.2.7. 우선순위가 같고 결합성이 다른 두 토큰
3.2.3 표에서는 한 단계 안의 토큰이 모두 같은 결합성을 가지므로 이런 경우는 생기지 않습니다. 확장 토큰도 like 로 기존 토큰의 결합성을 그대로 따르므로 마찬가지입니다.