5Operators
5.1 Arithmetic
| Operator | Description |
|---|---|
+ - * / % | 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
| Operator | Description |
|---|---|
== != | 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 10.6).
Unrelated types are not compared. Two values of different enum types, or two pointers neither of which converts to the other, are refused (E0200) by every comparison operator. An enum compares its discriminant, so A::Z == B::W would otherwise answer by variant position; two pointers to unrelated structs never hold the same address. A pointer still compares with null, with a void*, and with a pointer it converts to (T* with const T*):
type enum A { X; Z; }
type enum B { Y; W; }
const same: boolean = A::Z == B::W; // error[E0200]: cannot compare `A` with `B`
A reference is not compared with a plain value. When one operand is a reference and the other is a number, boolean, char, string or enum, the comparison is refused (E0229) - the same rule arithmetic follows, where x % 2 with x: &int is already an error. Dereference explicitly:
function is_seven(x: &i32) -> boolean {
return x == 7; // error[E0229]: a reference compared with a value
return *x == 7; // compares the value x refers to
}
The alternative - quietly dereferencing - was rejected because the comparison did not previously dereference: it compared the reference's address with 7, which compiled and was silently false. Refusing it makes the one meaning explicit at the call site. What stays allowed: two references compared with each other (by value when the referent implements Eq/Ord, by identity otherwise), and a reference compared with null.
5.3 Logical
| Operator | Description |
|---|---|
&& | Logical AND (short-circuiting) |
|| | Logical OR (short-circuiting) |
! | Logical NOT (unary) |
5.4 Bitwise
| Operator | Description |
|---|---|
& | ^ | 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.
| Operator | Description |
|---|---|
= | Simple assignment |
+= -= *= /= %= | Compound arithmetic |
&= |= ^= <<= >>= | Compound bitwise |
5.6 Other Operators
| Operator | Description |
|---|---|
-> | 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)). |
as | Explicit 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. |
new delete | Heap allocation / deallocation. |
Reserved.
?.and...in call position are recognised by the lexer but not yet lowered. See section 21. (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:
| Level | Operators | Associativity |
|---|---|---|
| 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 |
| 16 | as | Left |
| 17 | - ! & * ~ ++ -- (unary prefix), new, delete | Right |
| 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).
mut name: Type = value;A mutable binding, reassignable after declaration - mutability is opt-in, const is the default. At module level it declares mutable global state. Before & it marks an exclusive reference or receiver instead.
mut x: i32mut x: i32type struct Name { ... }
type enum Name { ... }
type trait Name { ... }
type union Name { ... }Begins a type declaration; the keyword that follows says which kind. On its own, type Name = Existing; declares an alias.
type enum Name {
Unit,
Payload(T),
Named { x: i32 },
}An algebraic data type. Variants may be bare or carry a tuple or struct payload, and a match on an enum must cover every variant. Name::Variant names a variant; methods are added with implement enum Name { ... }.
type enum AA::XA::Ztype enum BB::YB::Wconst NAME: Type = value;At module level, a true compile-time constant, conventionally SCREAMING_SNAKE_CASE. (mut at module level declares mutable global state instead.)
const same: booleantype enum Atype enum Bfunction name<T>(param: Type) -> Ret { ... }Declares a function. Parameters are written name: Type and are never inferred; the return type follows -> and defaults to void. Declaration order does not matter: every signature is collected before any body is checked, so functions may call each other freely and recurse.
function is_seven(x: &i32) -> boolean(parameter) x: &i32return value;Leaves the current function with the given value. A function whose return type is not void must return one; a bare return; is for void. In an async function it completes the future with the value.
(parameter) x: &i32const out: intvalue |> f(a, b)Pipeline: threads the left value into the call as its first argument, so x |> f(a) is f(x, a) and x |> f |> g is g(f(x)). A compile-time rewrite to an ordinary call, with no runtime cost.
function parse(text: Str) -> Result<JsonValue, JsonError>Parse a complete JSON document. Returns the parsed value on success, or a JsonError pointing at the first byte that failed to parse. Trailing whitespace is allowed; trailing non-whitespace produces JsonErrorKind::TrailingData.
const port: u16option ?? fallbackNull-coalescing: unwraps an Option<T> to its T, evaluating fallback only when it is None. Right-associative, so a ?? b ?? c reads a ?? (b ?? c) - every operand but the last is an Option.
function load(path: string) -> Result<Config, IoError>(parameter) path: stringprimitive stringNUL-terminated raw string (u8*), matching the C ABI. Length-typed text is Str / String.
type struct IoError {
kind: IoErrorKind;
os_code: i32;
}const name: Type = value;An immutable binding: it cannot be reassigned after initialisation. The annotation may be omitted when an initialiser is present - the binding takes the initialiser's concrete type, inferred locally. Mutability is opt-in through mut.
const text: Stringtype struct String<A = GlobalAlloc> {
buffer: RawBuffer<u8, A>;
length: u64;
}String: owned, heap-allocated, length-typed UTF-8.
std::collections::string(parameter) path: stringvalue?Error propagation. On a Result<T, E> it yields the T of an Ok and otherwise returns the Err(e) from the enclosing function unchanged; on an Option<T> it yields the T of a Some or returns None. The enclosing function must return a matching Result / Option.
Result::Ok(T)const text: Stringconst x: i32const ytypeof(expr)The static type of an expression, usable anywhere a type is expected: bindings, pointer and optional wrappers, generic arguments, as targets. The expression is type-checked but never evaluated.
const pconst n: i64const backconst n: i64value as TypeExplicit conversion: between numeric types, or reinterpreting one pointer type as another. Cryo never converts implicitly, and as inserts no range checks - a narrowing cast is the programmer's responsibility.
const x: i32const x: i32SigNo::SIGFPE = 8type enum Ordering {
Less;
Equal;
Greater;
}Ordering::LessOrdering::EqualOrdering::Greatertype trait Eq {
equals(&this, other: &This) -> boolean;
}Types that support value equality. equals(a, b) must be reflexive, symmetric, and transitive.
type trait Ord : Eq {
compare(&this, other: &This) -> Ordering;
}Types that have a total order: every pair is Less, Equal, or Greater. compare must be consistent with equals: if a.equals(b) then a.compare(b) == Equal.
type enum Atype enum BnullThe null pointer literal, valid in any pointer context - including the primitive string, which is a NUL-terminated u8*. Compare with == null; it is not an Option.
const Tconst pmatch (subject) {
Pattern(x) => { ... }
_ => { ... }
}The primary discriminator: branches on enum variants, integers, ranges, and destructuring patterns, and the compiler rejects a match that misses a case. There is no fallthrough. As an expression it evaluates to the chosen arm's value, so every arm must produce the same type.
Option::Some(T)Result::Err(E)Option::Nonef(a, b) <| valueBackward pipeline: appends the right value as the call's last argument, so f(a) <| x is f(a, x). Like |>, a compile-time rewrite with no runtime cost.
sizeof(T)The size of a type in bytes, as a compile-time constant - usable in static_assert. alignof(T) gives the alignment.
alignof(T)The alignment of a type in bytes, as a compile-time constant - usable in static_assert. sizeof(T) gives the size.
new T { field: value }
new E::Variant(args)
new T[n]Heap allocation. new T { .. } places a struct literal on the heap and yields a T*, and new E::V(args) does the same for an enum variant; new T[n] reserves n uninitialised T - the typed malloc. The storage comes from T's Allocate implementation, or else the global allocator. Parentheses after a struct's name are refused (E0117): there are no constructors, and a struct's static new(..) is an ordinary method.
delete p;Releases what new T { .. } allocated: the pointee's Drop and the values its fields own, then its storage, through T's Allocate::release. Memory from new T[n] or malloc is released with free instead.
const ystart..endA half-open range - start up to but excluding end - and sugar for Range::new(start, end): an iterator usable in for, and an ordinary value anywhere else. It binds looser than arithmetic, so a..b + 1 is a..(b + 1).
type struct Range<T> {
start: T;
end: T;
}static Range::new(start: T, end: T) -> Range<T>type struct RangeInclusive<T> {
start: T;
end: T;
exhausted: boolean;
}static RangeInclusive::new(start: T, end: T) -> RangeInclusive<T>