Skip to content

Primitives

NativeBrowserNode7 min read

Primitive types hold simple values: whole numbers, decimal numbers, booleans, and characters. Their methods are available without imports. See Types for sizes and literal forms.

At a glance

let n = int.parse("42").unwrap_or(0);
System.println(15.clamp(0, 10)); // 10
  • Integers, floating-point numbers, bool, and char are stored inline.
  • Their methods are available without imports.

Package: system.primitives (available without an import)

Primitive types are the built-in scalars: integers, floats, booleans, and characters. Their methods ship in the built-in library, so you can call them anywhere without an import. For the full type list and literal suffixes, see Types.

Primitive types

Type Size Range Literal
int 32-bit signed −2,147,483,648 to 2,147,483,647 42 (default for integer literals)
uint 32-bit unsigned 0 to 4,294,967,295 42u
long 64-bit signed −9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 42L
ulong 64-bit unsigned 0 to 18,446,744,073,709,551,615 42uL
isize Pointer width, signed Same as int on wasm32, same as long on 64-bit native No suffix; (isize)42
usize Pointer width, unsigned Same as uint on wasm32, same as ulong on 64-bit native No suffix; (usize)42
byte 8-bit unsigned 0 to 255 255b
float16 16-bit IEEE 754 (binary16) ±65,504, ~3 significant digits 1.5h
float 32-bit IEEE 754 About ±3.4 × 10³⁸, ~7 significant digits 3.14f, or bare 3.14
double 64-bit IEEE 754 About ±1.8 × 10³⁰⁸, ~15 significant digits 3.14d
bool — true or false true
char 32 bits One Unicode scalar value 'A', '\n'

Suffixes are case-insensitive. Integer literals can also be written in hex (0xFF), octal (0o77), or binary (0b101); a hex literal accepts only the u, L, and uL suffixes, because b, d, and f are hex digits.

Methods by type

Method Available on Returns Effect
.compare(other) Every numeric type, char int Negative, zero, or positive as this value is less than, equal to, or greater than other
.min(other) / .max(other) Every numeric type Same type The smaller / larger of the two values
.clamp(lo, hi) Every integer type Same type This value constrained to the inclusive range [lo, hi]
.abs() int, long, float16, float, double Same type Absolute value
.is_nan() / .is_infinite() float16 bool Whether the value is NaN / positive or negative infinity
.signum() int, long Same type -1, 0, or 1 by sign
.pow(exp) int int This value raised to exp; an exponent of 0 or less gives 1
.to_int() bool int 1 for true and 0 for false
.is_digit() / .is_alpha() / .is_whitespace() char bool ASCII classification
.to_lower() / .to_upper() char char ASCII case conversion; other characters are unchanged
.as_string() char string A new one-character string
Type.parse(text) Every primitive type Result<Type, ParseError> Static; parses decimal text, reporting bad or out-of-range input as Err

Because every numeric type and char implements compare, they all satisfy Comparable, so a List of them can be sorted directly.

Integers

Integers come in signed and unsigned forms in several widths (see the table above). Common methods:

  • .min(other) / .max(other) — the smaller / larger of two values.
  • .clamp(lo, hi) — constrain to the inclusive range [lo, hi].
  • .abs() and .signum() — available on int and long.
  • Type.parse(str) — static; parses a string into that integer type, returning Result<Type, ParseError>.
System.println(15.clamp(0, 10)); // 10
System.println((-5).abs()); // 5
let n = int.parse("42").unwrap_or(0); // 42

Integer overflow

Integer arithmetic wraps by default: an operation whose mathematical result does not fit its type wraps modulo the type's bit width (two's complement), in debug and release builds alike. Nothing is silently promoted to a wider type.

  • A binary op's result type is its left operand's type — byte + byte stays byte, so 250b + 10b is 4b.
  • +, -, *, and unary - wrap: 2147483647 + 1 is -2147483648, and uint 0u - 1u is 4294967295u.
  • / and % panic on a zero divisor. -2147483648 / -1 (the minimum signed value divided by -1) wraps to -2147483648, and the matching % is 0.
  • Shift counts are masked to the type's bit width: 1 << 33 on an int is 1 << 1. Bits shifted out of the value are discarded: 1 << 31 is -2147483648.
let i: int = 2147483647; // the largest int
let j = i + 1; // -2147483648

Wrap a checked { } block around code where an overflow is a bug you want to catch (sizes, offsets, money). Inside it, every integer op lexically in the block panics instead of wrapping:

Operation Default (wrapping) Inside checked { }
+, -, *, unary - Wraps around the type's bit width Panics when the result is outside the type
/, % by zero Panics Panics
/, % of the minimum signed value by -1 Wraps (/) or gives 0 (%) Panics
<<, >> Shift count is masked to the bit width Panics when the count is negative or at least the bit width

unchecked { } restores wrapping inside a checked region:

let count = 100000;
let size = 100000;
let h = 2166136261u;
checked {
    let total = count * size; // panic: attempt to multiply with overflow
    unchecked {
        h = (h ^ 97u) * 16777619u; // FNV-1a step: wraps at 32 bits
    }
}

The mode is lexical: it applies to the statements written inside the block (including lambdas written there), not to functions they call. checked and unchecked are only keywords directly before {, so they stay usable as identifiers.

Inside checked blocks, the optimizer removes checks it can prove never fire — a loop counter bounded by i < n, a masked x & 255, an array length.

Type.parse(str) reports out-of-range text as Err(ParseError) rather than panicking.

Floating point

IEEE 754, in three widths:

  • float16 — 16-bit half precision (1.5h). Compact storage for large arrays of approximate values such as machine-learning weights or color channels.
  • float — 32-bit (3.14f). An unsuffixed 3.14 is also float unless the expected type is double.
  • double — 64-bit. Use 3.14d for an always-double literal, or a bare 3.14 / 0 when the expected type is double.

Common methods:

  • .abs() — absolute value.
  • .min(other) / .max(other).
  • double.parse(str) — static; parses a string into a double, returning Result<double, ParseError>.
System.println((3.14f).abs()); // 3.14
System.println((1.5f).min(2.0f)); // 1.5
let d = double.parse("2.5").unwrap_or(0.0d);

Printing floating-point values

System.print, to_string, string concatenation, StringBuilder.append_double, and collection or struct formatting all write the same text for a floating-point value: the shortest decimal that rounds back to exactly that value in its own width. The output is identical on every target.

System.println(0.1d); // 0.1
System.println(0.1d + 0.2d); // 0.30000000000000004
System.println(1.0f / 3.0f); // 0.33333334 (float keeps about 7 digits)
System.println(3.0d); // 3

Magnitudes from 1e-7 up to, but not including, 1e21 print in plain notation, such as 0.000001 or 1500000. Smaller and larger magnitudes print with an exponent, such as 1e-7, 1.5e-8, or 1e+21. NaN prints as nan, the infinities as inf and -inf, and both zeros as 0.

Half precision: float16

A float16 keeps 11 significant bits (about 3 decimal digits) and holds finite values up to ±65,504. Values smaller than 6.1 × 10⁻⁵ are stored as subnormals, down to about 6 × 10⁻⁸.

let a: float16 = 1.5h;
let b = (float16)0.25;
let c = a * b + 2.0h; // 2.375
let wide: float = c; // float16 widens to float and double implicitly

System.println(c); // 2.375
System.println((float16)0.1); // 0.1 (stored as 0.0999755859375)
System.println(float16.max_value); // 65500

Conversions follow these rules:

  • float16 widens implicitly to float and double.
  • Converting a float or double to float16 needs a cast. The cast rounds to the nearest float16, choosing the even neighbor on a tie, and values beyond ±65,504 become infinity: (float16)2049.0 is 2048, and (float16)70000.0 is infinity.
  • Converting between integers and float16 needs a cast in both directions. Casting to an integer drops the fraction, and NaN becomes 0.
  • A float16 literal outside the finite range, such as 70000.0h, is a compile error.

Arithmetic and comparisons work like the other floating-point types. Each result is rounded to the nearest float16, so 2048.0h + 1.0h is 2048. Because the result of a binary operator has its left operand's type, put the wider operand first when mixing widths: 1.0f + a is a float, while a + 1.0f is a compile error.

Printing a float16 (or converting it with to_string) produces the shortest decimal text that reads back as the same value, so float16.parse(x.to_string()) always returns x. Infinity and NaN print as inf, -inf, and nan, and float16.parse accepts those words too.

Member Value
float16.max_value 65504, the largest finite value
float16.min_value -65504, the most negative finite value
float16.epsilon 0.0009765625 (2⁻¹⁰), the gap between 1 and the next float16
x.is_nan() true when x is NaN, for example 0.0h / 0.0h
x.is_infinite() true when x is positive or negative infinity
let parsed = float16.parse("0.125").unwrap_or(0.0h); // 0.125
let zero = 0.0h;
let nan = zero / zero;
System.println(nan.is_nan()); // true
System.println(((float16)1.0e10).is_infinite()); // true

@json classes encode float16 fields as JSON numbers using the same shortest text, and decoding rounds the number to the nearest float16. extern and @export functions cannot take or return float16, because host and C interfaces have no portable 16-bit float type; widen to float at those boundaries.

Booleans

bool is true or false.

  • .to_int() — 1 for true, 0 for false.
  • bool.parse(str) — static; accepts exactly "true" or "false" (case-sensitive), returning Result<Unit, ParseError>.
System.println(true.to_int()); // 1
let b = bool.parse("true").unwrap_or(false); // true
let bad = bool.parse("True").unwrap_or(false); // false — not exact "true"

Characters

char is a single character (one Unicode scalar value). Write literals in single quotes: 'A', '\n', 'é'.

  • .is_digit() / .is_alpha() / .is_whitespace() — classify the character (ASCII rules).
  • .to_lower() / .to_upper() — ASCII case conversion.
  • (int)c — the numeric code point of character c.
  • .as_string() — a new single-character string.
  • char.parse(str) — static; requires exactly one Unicode scalar in str; returns Result<char, ParseError>.
System.println('A'.is_alpha()); // true
System.println('A'.to_lower()); // 'a'
let s = 'H'.as_string(); // "H"
let c = char.parse("é").unwrap_or('?'); // 'é'