Skip to Content

Operators

Expressions are joined by operators. This page goes through all of them family by family; precedence rules are collected at the end.

Arithmetic

print(7 / 2, -7 / 2, 7 % 2, -7 % 2); print(2 + 3 * 4); print(2 ** 10, 2 ** 3 ** 2, -2 ** 2);
3 -3 1 -1 14 1024 512 -4
  • Integer division truncates toward zero: -7 / 2 is -3, not -4. The sign of the modulus follows the dividend, so -7 % 2 is -1.
  • ** is exponentiation and right-associative: 2 ** 3 ** 2 evaluates as 2 ** (3 ** 2), giving 512.
  • The operand of unary minus spans the whole power: -2 ** 2 parses as -(2 ** 2), giving -4. Write parentheses for (-2) ** 2.

Integer Overflow: Fixed-Width Wraparound

The default contract of integer arithmetic is unchecked fixed-width wraparound: a result that exceeds the width wraps around from the other end, with no error. Adding 1 to the int32 maximum wraps to the minimum; adding 1 to the uint32 maximum wraps to 0:

var m: int32 = 2147483647; print(m + 1); var u: uint32 = 4294967295; print(u + 1);
-2147483648 0

int64 behaves the same, and the result type stays unchanged. To check for overflow explicitly, use the Checked family on Math: CheckedAdd / CheckedSub / CheckedMul return null on overflow instead of wrapping:

print(Math.CheckedAdd(9223372036854775807, 1) == null); print(Math.CheckedMul(3037000500, 3037000500) == null);
True True

For the literal rules at each width and the Checked family in constant contexts, see Integers.

+ and * on Strings

+ concatenates as soon as one side is a string, stringifying the other side automatically; both directions are legal. * repeats when one side is a string and the other an integer:

print("n=" + 5, 5 + "=n", "ab" * 3);
n=5 5=n ababab

Concatenation inserts no separator; write any space or punctuation into the literal yourself.

Comparison and Equality

All six comparison operators < <= > >= == != return a bool. Ordering comparisons on strings are ordinal, comparing code points character by character:

print("Z" < "a", "ab" == "ab");
True True

The code point of uppercase Z is smaller than that of lowercase a, so "Z" < "a" holds.

Logical and Bitwise

The operands of &&, ||, and ! must be bools; an integer placed there directly is an error, so bit tests are written as explicit comparisons. The bitwise operators & | ^ ~ << >> act on integers:

print(true && false, true || false, !true); print(5 & 3, 5 | 3, 5 ^ 3, ~5, 1 << 4);
False True False 1 7 6 -6 16

Ternary

cond ? a : b is an expression, right-associative, and can nest:

val x = 5; print(x > 0 ? x > 3 ? "big" : "small" : "not positive");
big

The nesting reads as x > 0 ? (x > 3 ? "big" : "small") : "not positive". When a value-returning branch choice runs deeper than two levels, match is clearer.

Ranges and in

a..b is a half-open range and a..=b is closed; the bounds must be integers. in tests membership:

print(5 in 1..10, 10 in 1..10, 10 in 1..=10); val r = 1..5; print(3 in r, r);
True False True True 1..5

Ranges are values: store one in a variable and test against it later. in also accepts lists, strings (substring semantics), and other iterable types. The range operators do not associate; 1..2..3 is an outright MS2014 error. Stepped iteration is written for i in 0..10 step 3; see Loops.

Pipelines

The pipeline operator has four forms: a |> f is f(a), passing the left-hand value as an argument to the function on the right. The with-arguments form a |> f(x) is f(a, x). The null-safe pipeline a ?|> f short-circuits to null when the left side is null. The fallible pipeline a |>? f is try? f(a). The propagating pipeline a |>! f is try! f(a):

func Dbl(n: int) -> int => n * 2; func Add(a: int, b: int) -> int => a + b; print(21 |> Dbl, 21 |> Add(10));
42 31

The _ in the target expression is a placeholder: the left-hand value lands where the _ sits (xs |> _[0] and ", ".Join(_) both work). Chained pipelines flow left to right, which suits laying out a series of transformations in reading order. The full story on the four operators and the placeholder is in Pipelines.

Null-Conditional Operators

Nullable types (T?) come with a set of dedicated operators:

FormNameBehavior
o?.mnull-conditional memberThe whole expression is null when o is null
o?[i]null-conditional indexThe same, in index form
a ?? bnull coalescinga when non-null, otherwise b
a ??= bnull coalescing assignmentAssigns only when a is null
o!non-null assertionAsserts non-null; a null at runtime raises MS6045
var s: string? = null; print(s ?? "default", s?.Length ?? -1); s = "content"; print(s); var nickname: string? = null; nickname ??= "Anonymous"; print(nickname);
default -1 content Anonymous

Arithmetic over nullable values is itself nullable: if either side is null, the whole expression is null.

val a: int? = 2; val b: int? = null; print(a + 1, a + b ?? -1);
3 -1

a + b is null, and ?? -1 supplies the default. Note that ?? binds looser than all arithmetic, so a + b ?? -1 groups as (a + b) ?? -1.

For a fuller treatment of nullable types, see Nullable.

Precedence of with

with is a high-precedence suffix that attaches to the nearest expression on its left, not to the whole calculation:

print(vec2(1, 0) + vec2(2, 3) with { x: 9.0 });
vec2(10.0, 3.0)

What actually computes is 1,0 + (2,3 with x updated to 9), giving (10, 3). To update the whole sum, add parentheses. Full with usage is in Vectors and Colors.

Custom Operators

Classes and structs can overload operators: declare them with the bare operator form, and the first parameter must be the receiver self. The full set of overloadable operators: + - * / % == != < <= > >= & | ^ ~:

class Vec { public var x: int = 0; public var y: int = 0; public operator +(self, other: Vec) -> Vec { return new Vec() { x: self.x + other.x, y: self.y + other.y }; } } func main() -> int { val a = new Vec() { x: 1, y: 2 }; val b = new Vec() { x: 3, y: 4 }; val s = a + b; print(s.x, s.y); return 0; }
4 6

Operator overloads are also the satisfaction surface for protocol operator signatures: when a type impls a protocol containing operator signatures, it must supply a matching overload for each one. Overloading == / != takes over the type’s equality. For the language-wide ruling when nothing is overloaded, see Equality and Hashing. The conversion operator, conversion, is a separate declaration; see Conversions.

Notes

  • Assignment and compound assignment (= += -= *= /= %= **= &= |= ^= <<= >>= ??=) are right-associative; &&= and ||= do not exist.
  • Postfix i++ / i-- is statement sugar for i += 1.
  • Full precedence, lowest to highest: assignment → ternary → pipeline → || → && → ?? → equality and in → is as → relational → bitwise or → bitwise xor → bitwise and → shift → range → additive → multiplicative → power → unary → null-conditional → with → member and call.
Last updated on October 11, 2026