Skip to content

Operators

Operators are listed from highest precedence (binds tightest) to lowest.

PrecedenceOperatorsAssociativityDescription
1. () [] ! ?? ?LeftMember access, call, index, unwrap ops
2not - (unary)RightBoolean negation, numeric negation
3^RightExponentiation (power)
4* / %LeftMultiplication, division, modulo
5+ - ++LeftAddition/concatenation, subtraction, concat (legacy)
6.. ..=NoneExclusive range, inclusive range
7== != < > <= >=NoneComparison (non-associative)
8andLeftLogical AND (short-circuit)
9orLeftLogical OR (short-circuit)
10|> >>LeftPipe, function composition

Member Access, Calls, and Unwrap (. () [] ! ?? ?)

Section titled “Member Access, Calls, and Unwrap (. () [] ! ?? ?)”
user.name // field access
string.len(s) // module function call
xs[0] // index read
xs[i] = value // index write (var only)

Three postfix operators for unwrapping Result and Option values:

fs.read_text(path)! // unwrap or propagate error (effect fn only)
int.parse(s) ?? 0 // unwrap with fallback value
int.parse(s)? // convert Result to Option (discard error)
OperatorOn ResultOn OptionValid in
expr!ok(v) -> v, err(e) -> propagatesome(v) -> v, none -> propagateeffect fn only
expr ?? fallbackok(v) -> v, err(_) -> fallbacksome(v) -> v, none -> fallbackAnywhere
expr?ok(v) -> some(v), err(_) -> nonePassthroughAnywhere

See Error Handling for detailed examples.

not active // boolean negation
-x // numeric negation

There is no boolean ! operator. Use not for boolean negation. The postfix ! is the unwrap/propagate operator.

2 ^ 10 // => 1024
3.0 ^ 0.5 // => 1.732...
2 ** 3 // => 8 (** is an alias for ^)

Right-associative: 2 ^ 3 ^ 2 is evaluated as 2 ^ (3 ^ 2) = 2 ^ 9 = 512.

10 * 3 // => 30
10 / 3 // => 3 (integer division)
10 % 3 // => 1 (modulo)
5 + 3 // => 8
5 - 3 // => 2

The + operator is overloaded for strings and lists:

"hello" + " " + "world" // => "hello world"
[1, 2] + [3, 4] // => [1, 2, 3, 4]
0..5 // [0, 1, 2, 3, 4] (exclusive end)
1..=5 // [1, 2, 3, 4, 5] (inclusive end)
for i in 0..n { ... } // optimized: no list allocation

Ranges are non-associative — you cannot chain them.

x == y // deep equality
x != y // not equal
a < b // less than
a > b // greater than
a <= b // less than or equal
a >= b // greater than or equal

Comparison operators are non-associative: a < b < c is a compile error. Write a < b and b < c instead.

Deep equality works on all value types (records, variants, lists, maps). Function types do not support ==.

a > 0 and b > 0 // short-circuit AND
x == 0 or y == 0 // short-circuit OR

There are no && or || operators. Use and and or.

text |> string.trim |> string.split(",")
// with placeholder:
xs |> filter(_, (x) => x > 0) // _ = placeholder for piped value

The pipe operator passes the left-hand value as the first argument to the right-hand function. Use _ as a placeholder to control argument position.

let transform = string.trim >> string.to_upper
transform(" hello ") // => "HELLO"

Composes two functions left-to-right: (f >> g)(x) is equivalent to g(f(x)).

Used in record expressions to spread fields from another record:

let updated = { ...base, name: "bob" }
var x = 1
x = x + 1 // reassign (var only)
m["key"] = value // map index write (var only)
xs[i] = value // list index write (var only)
  • -> separates parameter types from return type in function signatures
  • => separates patterns/parameters from bodies in match arms and lambdas
fn add(a: Int, b: Int) -> Int = a + b
(x) => x + 1
match color { Red => "red", Blue => "blue" }

Bitwise operations are provided as stdlib functions, not operators:

int.band(a, b) // bitwise AND
int.bor(a, b) // bitwise OR
int.bxor(a, b) // bitwise XOR
int.bnot(a) // bitwise NOT
int.bshl(a, n) // shift left
int.bshr(a, n) // shift right