Skip to content
CryoCryo home
LanguageFoundations

5Operators

5.1 Arithmetic

OperatorDescription
+ - * / %Add, subtract, multiply, divide, modulo
- (unary)Negation
++ --Pre/postfix increment, decrement

Integer division truncates toward zero. The prefix forms ++x, --x evaluate to the new value; the postfix forms x++, x-- evaluate to the old value (C semantics).

Overflow. Integer arithmetic wraps on overflow using two's-complement modular arithmetic, for both signed and unsigned types: the result is reduced modulo 2N for an N-bit type. There is no overflow trap and no automatic widening. For example, with i32:

mut x: i32 = 2147483647;   // i32::MAX
x = x + 1;                 // wraps to -2147483648 (i32::MIN), no trap

This is a defined deterministic result, not undefined behavior, but it is silent: the language does not insert checks. Code that must detect overflow has to compare against the type's bounds before the operation. (Unsigned wrap is the usual mod 2N; e.g. 0u8 - 1u8 == 255.)

Division and modulo by zero are not checked by the compiler and fault at runtime (on typical targets the CPU raises SIGFPE); the signed i32::MIN / -1 / i32::MIN % -1 cases overflow the result and fault the same way. Guard the divisor when it can be zero.

5.2 Comparison

OperatorDescription
== !=Equal / not equal
< > <= >=Ordering comparisons
<=>Three-way comparison (spaceship); yields an Ordering (Less / Equal / Greater)

Comparison operators return boolean. On numeric types and pointers they emit native instructions; on user-defined types that implement Eq/Ord they are overloaded - a == b becomes a.equals(&b) and a < b becomes a.compare(&b).is_lt() (see operator overloading, section 11.6).

5.3 Logical

OperatorDescription
&&Logical AND (short-circuiting)
||Logical OR (short-circuiting)
!Logical NOT (unary)

5.4 Bitwise

OperatorDescription
& | ^AND / OR / XOR
~Bitwise NOT (unary)
<< >>Left / right shift (arithmetic on signed, logical on unsigned)

5.5 Assignment

Only mut bindings can be assigned to.

OperatorDescription
=Simple assignment
+= -= *= /= %=Compound arithmetic
&= |= ^= <<= >>=Compound bitwise

5.6 Other Operators

OperatorDescription
->Return-type arrow. Not a member-access operator: . auto-dereferences pointers, so write p.field, never p->field.
=>Pattern-to-body separator inside match.
::Scope resolution: static methods, enum variants, module members.
? :Ternary conditional.
? (postfix)Error propagation ("try"): on a Result/Option, yields the Ok/Some payload, else returns the Err/None from the enclosing function.
??Null-coalescing: opt ?? fallback yields a Some's payload, else fallback (evaluated only when opt is None).
|> <|Pipeline: thread a value into a call (x |> f(a) => f(x, a); f(a) <| x => f(a, x)).
asExplicit type cast.
.Member access.
&Address-of (unary).
*Dereference (unary).
sizeof(T)Compile-time size of T in bytes.
alignof(T)Compile-time alignment of T in bytes.
typeof(expr)Compile-time type of expr, used in type position (decltype-style).
new deleteHeap allocation / deallocation.

Reserved. ?. and ... in call position are recognised by the lexer but not yet lowered. See section 22. (The range operators .. / ..= are fully lowered - see section 5.7.)

Pipeline (|>, <|). The pipeline operators thread a value into a call. x |> f is f(x); with an argument list the piped value is prepended - x |> f(a, b) is f(x, a, b). The backward form appends instead - f(a, b) <| x is f(a, b, x). Pipes are left-associative, so x |> f |> g is g(f(x)). They are a compile-time rewrite to an ordinary call, with no runtime cost.

const out: int = data |> parse |> validate(strict);   // validate(parse(data), strict)

Null-coalescing (??). opt ?? fallback unwraps an Option<T> to its T, substituting fallback when it is None. The left operand is evaluated once and fallback only when needed. ?? is right-associative and binds looser than the pipes, so a chain reads as a ?? (b ?? c): every operand but the last is an Option<T>, and the final c is the bare T.

const port: u16 = config_port() ?? env_port() ?? 8080;

Error propagation (?). A postfix ? on a Result<T, E> evaluates to T when the value is Ok, and otherwise returns that Err(e) unchanged from the enclosing function; on an Option<T> it yields T for Some and returns None. The enclosing function's return type must be a matching Result / Option. It is the concise form of a match that re-returns the error.

function load(path: string) -> Result<Config, IoError> {
    const text: String = read_file(path)?;     // returns Err(e) on failure
    return Result::Ok(parse_config(text));
}

Type-of (typeof). typeof(expr) resolves to the static type of expr and is used in type position - anywhere a type annotation is expected: variable bindings, pointer/array/optional wrappers, generic arguments, and as cast targets. It is a compile-time construct that names a type, not a value, so it cannot appear where a value is expected. expr is only type-checked, never evaluated.

const x: i32 = read_count();
const y: typeof(x) = 0;        // y : i32
const p: typeof(x)* = &x;      // p : i32*
const n: i64 = 5;
const back = n as typeof(x);   // back : i32

5.7 Operator Precedence

From lowest to highest:

LevelOperatorsAssociativity
1= += -= *= /= %= &= |= ^= <<= >>=Right
2?? (null-coalescing)Right
3|> <| (pipeline)Left
4? : (ternary)Right
5.. ..= (range)Left
6||Left
7&&Left
8|Left
9^Left
10&Left
11== !=Left
12< > <= >= <=>Left
13<< >>Left
14+ -Left
15* / %Left
16asLeft
17- ! & * ~ ++ -- (unary prefix), new, deleteRight
18() [] . ? (postfix try) ++ -- (postfix)Left

as sits between multiplication and unary, so x * y as i64 casts y, not the product. Use parentheses if you mean (x * y) as i64.

The range operators .. / ..= bind looser than every arithmetic and comparison operator, so a + 1 .. b * 2 is (a + 1) .. (b * 2). They desugar at parse time to Range::new / RangeInclusive::new (see section 6.3).