Precedence
2 + 3 * 4 could mean 20 or 14, depending on which operator goes first. Maths settled this long ago — multiplication before addition — and Rux follows the same idea for every operator it has. The rules that decide the order are called precedence, and knowing the few that matter lets you read an expression the way the compiler does.
The ranking
From tightest to loosest, the operators of this part rank like this:
| Rank | Operators | Kind |
|---|---|---|
| tightest | x++ x-- | step after reading |
-x !x ++x --x | unary | |
as | conversion | |
* / % | multiplication | |
+ - | addition | |
< <= > >= | ordering | |
== != | equality | |
&& | and | |
|| | or | |
| loosest | = += -= … | assignment |
A tighter operator grabs its operands first. Parentheses override all of it.
PrintLine("2 + 3 * 4 = {}", 2 + 3 * 4);
PrintLine("(2 + 3) * 4 = {}", (2 + 3) * 4);
* ranks above +, so 3 * 4 happens first and the answer is 14. Wrap 2 + 3 in parentheses and it is 20. % ranks with *, not with +, so 7 + 10 % 4 is 7 + 2, which is 9.
Same rank, left to right
When operators share a rank, they group from the left:
PrintLine("100 / 10 / 5 = {}", 100 / 10 / 5);
PrintLine("100 / (10 / 5) = {}", 100 / (10 / 5));
PrintLine("10 - 3 - 2 = {}", 10 - 3 - 2);
100 / 10 / 5 is (100 / 10) / 5, which is 2. Grouping it the other way gives 50. For + and * the grouping never changes the answer, but for - and / it does.
as converts only its neighbour
as binds tighter than any arithmetic, so it converts the single operand right next to it — not the whole expression on its left:
let a: int32 = 17;
let b: int32 = 5;
PrintLine("(a / b) as float64 = {}", (a / b) as float64);
PrintLine("a as float64 / b as float64 = {}", a as float64 / b as float64);
The first line divides two integers, losing the fraction, and only then converts: 3.0. The second converts each integer first and divides two floats: 3.4. Here is how the compiler groups both:
flowchart LR
e1["(a / b) as float64"] --> d1["a / b = 3<br/>integer division"] --> c1["3 as float64<br/>= 3.0"]
e2["a as float64 / b as float64"] --> c2["17.0 and 5.0<br/>converted first"] --> d2["17.0 / 5.0<br/>= 3.4"]Writing a / b as float64 would convert only b, and then / would have an int32 on one side and a float64 on the other — which the compiler refuses.
Conditions read naturally
Arithmetic ranks above comparison, and comparison above && and ||. That order is chosen so that the usual conditions need no parentheses at all:
let age: int32 = 25;
PrintLine("age >= 18 && age < 65 is {}", age >= 18 && age < 65);
The comparisons are worked out first, then && combines their answers. Between the two logical operators, && binds tighter than ||, the same way * binds tighter than +:
PrintLine("true || false && false is {}", true || false && false);
PrintLine("(true || false) && false is {}", (true || false) && false);
The first is true || (false && false), which is true. And ! applies only to the operand right next to it, so !ready && busy is (!ready) && busy — use parentheses, !(ready && busy), to negate the whole thing.
When in doubt, add parentheses
Precedence decides what the compiler does; parentheses decide what the reader sees. a * b + c is clear to anyone, but x || y && z makes most people stop and think. Parentheses that match the default grouping change nothing in the program and save the next reader that pause.
The program
The whole lesson is one package in the Examples repository. Its comments explain every step.
// When an expression mixes operators, precedence decides which applies first,
// the way multiplication comes before addition in maths. From tightest to
// loosest, the operators seen so far rank like this:
//
// x++ x-- step after reading
// -x !x ++x --x unary
// as conversion
// * / % multiplication
// + - addition
// < <= > >= ordering
// == != equality
// && and
// || or
// = += -= ... assignment, loosest of all
//
// Operators on the same rank group from left to right. Parentheses override
// all of it, and are worth adding wherever a reader would have to stop and think.
import Io::PrintLine;
func Main() -> int {
// Multiplication before addition, unless parentheses say otherwise.
PrintLine("2 + 3 * 4 = {}", 2 + 3 * 4);
PrintLine("(2 + 3) * 4 = {}", (2 + 3) * 4);
// `%` ranks with `*`, not with `+`.
PrintLine("7 + 10 % 4 = {}", 7 + 10 % 4);
// Same rank, left to right: 100 / 10 first, then / 5. Grouping the other
// way round gives a different answer.
PrintLine("100 / 10 / 5 = {}", 100 / 10 / 5);
PrintLine("100 / (10 / 5) = {}", 100 / (10 / 5));
PrintLine("10 - 3 - 2 = {}", 10 - 3 - 2);
// `as` binds tighter than arithmetic, so it converts only its neighbour.
// Converting the result of an integer division is too late: the fraction
// was already gone.
let a: int32 = 17;
let b: int32 = 5;
PrintLine("(a / b) as float64 = {}", (a / b) as float64);
PrintLine("a as float64 / b as float64 = {}", a as float64 / b as float64);
// Arithmetic happens before comparing, and comparing before `&&`, so a
// range check needs no parentheses at all.
let age: int32 = 25;
PrintLine("age >= 18 && age < 65 is {}", age >= 18 && age < 65);
// `&&` binds tighter than `||`, the same way `*` binds tighter than `+`.
PrintLine("true || false && false is {}", true || false && false);
PrintLine("(true || false) && false is {}", (true || false) && false);
// `!` applies only to the operand right next to it.
let ready = true;
let busy = false;
PrintLine("!ready && busy is {}", !ready && busy);
PrintLine("!(ready && busy) is {}", !(ready && busy));
return 0;
}
Run it
cd Examples/Operators/Precedence
rux run
2 + 3 * 4 = 14
(2 + 3) * 4 = 20
7 + 10 % 4 = 9
100 / 10 / 5 = 2
100 / (10 / 5) = 50
10 - 3 - 2 = 5
(a / b) as float64 = 3.0
a as float64 / b as float64 = 3.4
age >= 18 && age < 65 is true
true || false && false is true
(true || false) && false is false
!ready && busy is false
!(ready && busy) is true
Common mistakes
(a / b) as float64 is 3.0, not 3.4: the integer division has already dropped the fraction. Convert each operand before dividing.as to cover the whole expression.a / b as float64 converts only b, and fails with error: operator '/' cannot combine left operand 'int32' with right operand 'float64'.!count == 0 is (!count) == 0, and when count is an int32 it fails with error: operator '!' requires a bool operand, but found 'int32'. Write count != 0, or wrap what you mean in parentheses.Try it yourself
- Predict, then print,
2 * 3 + 4 * 5,20 - 4 - 6and8 / 2 * 4. - Add parentheses to
true || false && falsethat do not change its value, and then some that do. - Write the condition "age is under 13 or over 65, and has a ticket" with an
int32ageand aboolticket. Which parentheses are required?
Learn more
- Operator precedence in the Rux Reference
- Arithmetic, Comparison and Logical — the operators being ranked
- Ternary — the conditional operator, which ranks just above assignment