Reference / Operators
Operators
An operator is a symbol or a keyword that makes a new value from one or two operands, such as + in a + b.
fn main() {
let a = 20
let b = 22
print(a + b) // 42
print(a < b && b < 30) // true
let o: i64? = None
print(o ?? a) // 20
}
Precedence
When an expression has more than one operator, the operator with the higher precedence applies first. So 1 + 2 * 3 is 1 + (2 * 3). Parentheses change the order.
fn main() {
print(1 + 2 * 3) // 7
print((1 + 2) * 3) // 9
print(10 - 4 - 3) // 3
}
This table lists the operators from the highest precedence to the lowest:
| Operators | Kind | Grouping |
|---|---|---|
., ?., [], a call f(...) |
Postfix | From left to right |
-, ! |
Unary prefix | From right to left |
*, /, % |
Multiplication | From left to right |
+, - |
Addition | From left to right |
?? |
Default value | From right to left |
<, <=, >, >= |
Comparison | A chain |
==, !=, is |
Equality | Does not chain |
&& |
And | From left to right |
|| |
Or | From left to right |
The rules:
- A postfix operator applies before a unary operator. So
-2.5.floor()is-(2.5.floor()). - A unary operator applies before a binary operator. So
-2 * 3is(-2) * 3. ??applies after+and before a comparison. Soo ?? 1 + 2iso ?? (1 + 2), ando ?? 0 < 5is(o ?? 0) < 5.a ?? b ?? cisa ?? (b ?? c).- A comparison and an equality do not group from left to right: see Comparison chains and Equality.
fn main() {
print(-2.5.floor()) // -2.0
print((-2.5).floor()) // -3.0
let o: i64? = None
print(o ?? 1 + 2) // 3
print(o ?? 0 < 5) // true
}
The program calculates the operands of a binary operator from left to right, then applies the operator. &&, || and ?? can skip their right operand, as their sections tell.
A .. or ..= range is not an operator. It is part of a for loop or a pattern, and is an error in other places (E0023). The section Loops over iterables is in development, and it makes a range a value.
Arithmetic
+, -, *, / and % take two numbers of one type: two i64s, two u8s or two f64s. The result has that type. + also joins two strings.
fn main() {
let b: u8 = 200
print(7 + 2 * 3) // 13
print(b + 55) // 255
print(1.5 * 4.0) // 6.0
print("sau" + "na") // sauna
}
The rules:
- The two operands must have one type. Sisu does not convert an operand to the type of the other (E0032). Integer literals are the exception (Literals).
- Arithmetic does not apply to a
char(E0094), abool, an optional or an array. - A
+with astringand another type is an error. Use interpolation, such as"n = {n}". - Sisu has no
**operator (E0107). For a power, use thef64methodpow(Types).
fn main() {
let n = 2
let x = 1.5
print(n * x) // error[E0032]
}
E0032: what this error means
Division and remainder
On integers, / gives the quotient without its fraction: it rounds toward zero. % gives the remainder, which has the sign of the left operand. So a == a / b * b + a % b is always true.
fn main() {
print(7 / 2) // 3
print(-7 / 2) // -3
print(7 % 3) // 1
print(-7 % 3) // -1
print(7 % -3) // 1
}
On f64, / is the IEEE 754 division. % gives the remainder of a division that rounds toward zero, with the sign of the left operand.
fn main() {
print(7.0 / 2.0) // 3.5
print(7.5 % 2.0) // 1.5
print(-7.5 % 2.0) // -1.5
}
Overflow
At run time, an integer +, - or * whose result is outside the range of its type stops the program with a panic. Sisu never wraps a result. These operations also panic with an overflow:
- a unary
-on the smallesti64 /and%when the left operand is the smallesti64and the right operand is-1
fn main() {
let big = 9223372036854775807
print(big * 2)
}
sisu: panic at example.sisu:3:11: integer overflow
A u8 result must be from 0 to 255, so a u8 subtraction below zero panics too.
fn main() {
let a: u8 = 1
let b: u8 = 2
print(a - b)
}
sisu: panic at example.sisu:4:11: integer overflow
f64 arithmetic never panics. A result that is too large is an infinity (Types).
Division by zero
At run time, / or % on integers with a right operand of zero stops the program with a panic.
fn main() {
let zero = 0
print(7 / zero)
}
sisu: panic at example.sisu:3:11: division by zero
On f64, a division by zero does not panic. It gives an infinity or NaN.
fn main() {
print(7.0 / 0.0) // inf
print(7.0 % 0.0) // nan
}
Arithmetic in a constant
The program calculates a constant when it builds. So an integer overflow or an integer division by zero in a constant is an error, not a panic (E0088).
const LIMIT = 9223372036854775807 + 1 // error[E0088]
fn main() {
print(LIMIT)
}
E0088: what this error means
Floating-point arithmetic in a constant follows the rules that it has at run time. A division by zero gives an infinity or NaN, and is not an error.
const INFINITY = 1.0 / 0.0
const NAN = 0.0 / 0.0
fn main() {
print(INFINITY) // inf
print(NAN) // nan
}
Unary operators
A unary - changes the sign of an i64 or an f64. ! gives the opposite of a bool.
fn main() {
let n = 5
let x = 2.5
print(-n) // -5
print(-x) // -2.5
print(!true) // false
}
The rules:
- A unary
-on au8is an error, because au8cannot be negative (E0042). !takes only abool(E0032).- A
-before an integer literal is a unary-, not part of the literal (Literals).
Comparison
<, <=, > and >= compare two values of one type and give a bool. The types are i64, u8, f64, char and string.
fn main() {
print(2 < 3) // true
print(2.5 >= 3.0) // false
print('a' < 'b') // true
print("apple" < "banana") // true
}
The rules:
- A
charcompares by its code point. Astringcompares byte by byte, which is the order of the code points. So the ASCII upper case letters come before the ASCII lower case letters, and a prefix comes before the longer string. - An
f64comparison follows IEEE 754: a comparison with NaN is false. - A
bool, an optional, an array, a class or an enum cannot be compared with<(E0032). - The two operands must have one type (E0032).
Comparison chains
A comparison chain such as a < b < c is true when each comparison in it is true. Here, these are a < b and b < c. The program calculates each operand at most once, and stops at the first comparison that is false.
fn main() {
let n = 5
print(0 <= n < 10) // true
print(1 < 2 < 2) // false
print("a" < "b" <= "b") // true
}
A chain goes in one direction: all its operators are < and <=, or all are > and >= (E0025).
fn main() {
print(1 < 2 > 0) // error[E0025]
}
E0025: what this error means
Equality
== is true when two values of one type are equal. != is its opposite.
fn main() {
print(2 + 2 == 4) // true
print("sauna" != "sisu") // true
print([1, 2] == [1, 2]) // true
let o: i64? = 5
print(o == 5) // true
print(o == None) // false
}
The rules:
- Two arrays are equal when they have the same length and their elements are equal, in order. Two enum values are equal when they are the same variant and their payloads are equal.
- A
Tbeside aT?becomes aT?. Two optionals are equal when both areNone, or both hold equal values. x == Noneandx != Nonetest whether the optionalxholds a value. Whenxis not optional, the test is an error (E0044).- Values of two types cannot be compared (E0047, E0032).
- An
f64follows IEEE 754: NaN is not equal to itself, and-0.0 == 0.0is true. So an array that holds NaN is not equal to itself. ==,!=andisdo not chain:a == b == cis an error (E0024). Join two tests with&&.
fn main() {
let a = 1
let b = 1
print(a == b == true) // error[E0024]
}
E0024: what this error means
is
a is b is true when a and b refer to the same object. It applies to classes, and to optionals of classes. When two optionals are both None, a is b is true. An object and None are not the same.
class Sauna {
heat: i64
}
fn main() {
let a = Sauna(heat: 80)
let b = Sauna(heat: 80)
let c = a
print(a is c) // true
print(a is b) // false
print(a.heat == b.heat) // true
let x: Sauna? = None
let y: Sauna? = None
print(x is y) // true
print(x is a) // false
}
is on a value that is not an object, such as an i64, a string, an array or an enum value, is an error (E0046). To test an optional for None, use == None (E0045). Values and memory compares is with ==.
Logical operators
&& is true when both operands are true. || is true when one or both operands are true. The operands must be bools.
fn main() {
let n = 7
print(n > 0 && n < 10) // true
print(n < 0 || n == 7) // true
}
&& and || calculate the right operand only when it can change the result. When the left operand of && is false, or the left operand of || is true, the right operand does not run.
fn check(b: bool) -> bool {
print("checked")
return b
}
fn main() {
print(false && check(true))
print(true || check(false))
print(true && check(true))
}
false true checked true
Default values
a ?? b gives the value in the optional a. When a is None, it gives b. The program calculates b only when a is None.
fn main() {
let found: i64? = 7
let missing: i64? = None
print(found ?? 0) // 7
print(missing ?? 0) // 0
}
The rules:
- The left operand must be an optional (E0048).
- When
ais aT?andbis aT, the result is aT. - When
bis aT?too, the result is aT?. Soa ?? b ?? 0triesa, thenb, then gives0. bcan be an expression that never finishes, such aspanic("...")(Types). Then the result is aT.
fn main() {
let a: i64? = None
let b: i64? = 3
print(a ?? b ?? 0) // 3
print(b ?? panic("no b")) // 3
}
Optional chaining
a?.name reads the field name of the optional a, and a?.name(...) calls its method. When a is None, the result is None. Then the method and its arguments do not run.
class Room {
name: string
next: Room?
}
fn main() {
let hall = Room(name: "hall", next: Room(name: "sauna", next: None))
print(hall.next?.name ?? "none") // sauna
print(hall.next?.next?.name ?? "none") // none
}
The rules:
- The result is an optional of the type of the field or the method's result. When that type is already an optional, the result is that optional, not an optional of an optional. So a chain such as
a?.b?.cgives one optional. a?.b.cis an error whenbis not optional, becausea?.bis an optional (E0062). Writea?.b?.c.- A method that returns no value has no value through
?.too (E0043). WhenaisNone, the method and its arguments do not run. ?.on a value that is not optional is an error (E0049). Use..- An assignment cannot go through
?.(E0022). - A line that starts with
?.continues the line above it (Lexical structure).
class Sauna {
heat: i64
fn show(self, label: string) {
print("{label}: {self.heat}")
}
}
fn label() -> string {
print("label")
return "heat"
}
fn main() {
let on: Sauna? = Sauna(heat: 80)
let off: Sauna? = None
on?.show(label())
off?.show(label())
}
label heat: 80
Passing a failure up In development
x? on a result gives the success, or ends the enclosing function with the failure. On an optional, x? gives the value, or ends the function with None. r ?? d on a result gives the success, or d on any failure (The ? operator).
fn parse(text: string) -> i64 | string {
i64(text) ?? fail "not a number: {text}"
}
fn twice(text: string) -> i64 | string {
parse(text)? * 2
}
fn main() {
print(twice("21") ?? 0) // 42
}
? is a postfix operator and binds as a call does.
Sisu