3.2. Precedence and associativity
Operators have two properties: precedence and associativity. When several operators appear in one expression, these two properties determine the shape of the resulting tree.
3.2.1. Precedence
a + b * c → a + (b * c) // * binds tighter than +
a * b + c → (a * b) + c // likewise
a < b + c → a < (b + c) // + binds tighter than <
Precedence values are integers; an operator with a larger value binds first.
3.2.2. Associativity
a - b - c → (a - b) - c // left-associative
a ?? b ?? c → a ?? (b ?? c) // right-associative (example)
a < b < c → ? // non-associative (chaining banned)
Associativity is one of three kinds.
| Notation | Meaning |
|---|---|
left | Groups from the left. a OP b OP c = (a OP b) OP c |
right | Groups from the right. a OP b OP c = a OP (b OP c) |
none | Chaining banned. a OP b OP c is a compile error |
3.2.3. Precedence and associativity table
The language fixes the precedence and associativity of every built-in operator token; the table below is the single authority. The standard library's #[infix] / #[prefix] / #[postfix] only connect tokens to trait methods; they do not set or change precedence or associativity.
Precedence levels
Level Operators Assoc
13 postfix: member access (.), ?., method call, indexing, ? left
12 prefix unary: - ! ~ √ await -
11 as is (postfix conversion / type test) left
10 << >> left
9 ∘ (function composition) left
8 * / % %% × ⋅ ÷ ∩ left
7 + - ∪ left
6 & ^ left
5 | left
4 == != < > <= >= ≡ ≠ ≤ ≥ ∈ ∉ ⊂ ⊆ none
3 && left
2 || left
1 ?? (null coalescing) right
0 .. ..= (range) none
-1 = += -= *= /= %= %%= &= |= ^= <<= >>= (assignment) right
The Unicode operators in the table (× ⋅ ÷ ∩ ∪ ∘ √ ≡ ≠ ≤ ≥ ∈ ∉ ⊂ ⊆) are fixed tokens of 1.10.3. Extension tokens not in the 3.2 table (1.10.4) do not create new levels; they take the level and associativity of the existing token named at registration, as in like = "+" (6.7.2).
Important
Relative precedence of ?? and ..: ?? (prec=1) binds tighter than the range operators .. / ..= (prec=0). So a ?? 0..3 groups as (a ?? 0)..3, and the range operators are weaker than every binary operator except assignment (a || b..c is (a || b)..c). Neither operator dispatches through a trait; the language handles both directly - ?? is a language-level desugaring over sum types (3.8.4), and ranges are syntax whose six forms the language recognizes as distinct expressions (3.7). The range operators are non-associative, so a..b..c is a compile error.
Important
Binding of try with catch / finally: try / try? / try! and the catch / finally clauses of error control flow are also language syntax with no trait connection. try is a prefix form, but unlike the prefix unary operators in the table (prec=12) it covers the entire expression to its right. Its operand extends up to a catch / finally clause, the statement terminator ;, a closing bracket, or a comma, so try a + b() is try (a + b()). The catch / finally clauses bind tighter than assignment (prec=-1) and weaker than every other operator, attaching to the whole expression on their left. Thus try parse(s) catch _ { 0 } is (try parse(s)) catch _ { 0 }, and x = try f() catch _ { 0 } is x = ((try f()) catch _ { 0 }).
Important
await: await is a prefix unary operator at prec=12, like - and !, so its operand is a unary expression: await f() == x is (await f()) == x (4.12.2).
Important
as and is: these two postfix operators are language syntax with no trait connection and share one level (prec=11). They are weaker than prefix unary and stronger than arithmetic, so -x as i64 is (-x) as i64 and a + b is i32 is a + (b is i32). The result of e is T is bool (2.2.5).
Note
Precedence of dot-prefixed (broadcast) operators: dot-prefixed forms such as .+, .*, .&&, .%% have the same precedence and associativity as their undotted forms. They are not listed as separate rows; every dot-prefixed token takes the level of its corresponding plain token. For example, .+ is prec=7 left like +, and .&& is prec=3 left like &&.
Note
Unlike C, shift operators bind tighter than arithmetic. So 1 + 2 << 3 groups as 1 + (2 << 3) = 17. If this is counterintuitive, use explicit parentheses.
Multiple positions of the same token
When the same token can appear as prefix, binary, and postfix (for example -), each position has its own precedence (prefix is usually stronger than binary).
-a + b → (-a) + b // prefix - binds tighter than +
3.2.4. Conflicts
Because the table fixes precedence, registrations cannot conflict on precedence. Connecting different traits to the same (token, position) is a compile error per 6.7.5.
3.2.5. No comparison chaining
Warning
Chaining by mathematical intuition (a < b < c meaning a < b and b < c) is not allowed in rok.
The comparison operators (<, >, <=, >=, ==, !=) are all non-associative in the 3.2.3 table.
a < b < c // ERROR: chaining banned
a == b == c // ERROR
The intended meaning must be written explicitly:
a < b && b < c
There are two reasons for this decision.
- The result of a comparison is
bool, and comparing thatboolagain contradicts mathematical intuition. - Accepting chaining would require a separate chaining rule inconsistent with the other operators.
Writing && explicitly is therefore the simplest and most consistent form.
3.2.6. Forcing grouping with parentheses
When the tree produced by these rules differs from the intended shape, parentheses force the grouping.
a + b * c → a + (b * c) // default grouping
(a + b) * c → (a + b) * c // explicit
Parentheses act as grouping tokens in the lexical and syntactic phases and directly control the shape of the tree.
3.2.6a. Implementation limits
A chain of more than 4096 operators / postfix operations, or nesting deeper than 256, is an error. These are rok's limits (implementation-defined, 0.4.4).
3.2.7. Two tokens with the same precedence and different associativity
In the 3.2.3 table all tokens in one level share the same associativity, so this case does not arise. Extension tokens also take the associativity of an existing token through like, so the same holds.