Operators and arithmetic
Default arithmetic: checked in debug, wraps in release
let a: i32 = 10 + 20;
let c: i32 = 10 / 3; // 3
let d: i32 = 10 % 3; // 1
let e: i32 = 10 - 20; // -10
Default arithmetic is overflow-checked in debug builds and wraps in release. Division by zero always traps, in both modes.
Wrapping operators: always wrap
When you genuinely want wrap-on-overflow, use the %-suffixed family. They make the intent explicit, because silent overflow is a footgun you should opt into:
let a: u8 = 250u8 +% 10u8; // 4
let b: i8 = 100i8 *% 3i8; // overflows, wraps silently
let c: i32 = 0 -% 1; // -1, the canonical "this can underflow" idiom
Bitwise and shifts
let h: i32 = 0xff & 0x0f; // 15
let i: i32 = 0xf0 | 0x0f; // 255
let j: i32 = 0xff ^ 0xaa; // 85
let k: i32 = 1 << 8; // 256
let l: i32 = 256 >> 2; // 64
let m: u32 = ~(0 as u32); // 0xffffffff
Right shift on a signed type is arithmetic (sign-preserving); on an unsigned type it is logical (zero-fill).
Byte-swap intrinsics
let port_be: u16 = #htons(8080 as u16);
let n: u32 = #bswap32(0x12345678 as u32);
htons / htonl convert host order to network order. bswap16 / bswap32 / bswap64 are unconditional swaps.
Comparisons
let lt: bool = a < b;
let eq: bool = a == b; // no coercion
let ne: bool = a != b;
Casts: every width change is explicit
let x: i64 = 5;
let y: i32 = x as i32;
let f: f64 = (x as f64);
let z: usize = 10 as usize;
There is no int to bool, and no silent narrowing. A pointer and an integer convert only through usize, never through i32.
Checked narrowing: as?
n as? u8 evaluates to Option[u8]: Some(converted) when the value is representable in the target, None otherwise. Integer source and target only (E0315 names the offending side). The infallible as keeps its truncating semantics for the cases where truncation is intended.
import "stdlib/option" as option;
fn as_byte(n: i32) -> option::Option[u8] {
return n as? u8;
}
Codegen is a 64-bit bounds check selecting the Option tag and payload. Needs stdlib/option in the build.