Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Numbers reference

The sized numeric family (std::number), generic min/max (std::math), and random values (std::random). Literal syntax and conversion semantics: Values and types.

The family

TypeWidthLiteral
i8 i16 i32 i53signedbare = i32; others suffixed (100i53)
u8 u16 u32 u53unsignedsuffixed (0xFFu8)
f64float2.5 or 10f
f32float2.5f32
BigIntarbitrary7n

i53/u53 are the wide integers, named for the precision they actually deliver: they are f64-backed on the JS backend, and every value in ±2^53 (f64’s exact-integer window, one past JavaScript’s Number.MAX_SAFE_INTEGER) is exact. There is no i64: a type that silently loses precision past 2^53 would be lying about its width; for bigger integers use BigInt.

Literals are range-checked at compile time (an out-of-range i53 literal is a compile error, not a rounded value). Integer division truncates toward zero. No implicit width coercion; convert with as_*. Arithmetic that overflows a type’s range is undefined behavior (spec §7.2a): on JS it manifests as f64 artifacts; a checked add_safe family is recorded future work.

Because overflow is undefined, the boundary values have to be askable. Every integer type carries its two bounds as niladic functions:

fun main() {
	print(i32::max_value());   // 2147483647
	print(i32::min_value());   // -2147483648
	print(u8::max_value());    // 255
	print(i53::min_value());   // -9007199254740992
}
typemin_value()max_value()
i8-128127
u80255
i16-3276832767
u16065535
i32-21474836482147483647
u3204294967295
i53-90071992547409929007199254740992
u5309007199254740992

This spelling is a stopgap. vilan has no associated constants — there is no static-member mechanism for i32::MAX to hang on — so the bounds ship as functions rather than wait for that design. When it lands they become i32::MAX/i32::MIN and this pair enters a [deprecated("steer")] window that rewrites callers. The rename is scheduled, not a surprise: reach for max_value()/min_value() freely today.

The pair reports the type’s range, which is deliberately not the range of literals the compiler admits: 128i8 compiles, because the signed literal check tests the magnitude so that -128i8 can be written at all, yet i8::max_value() is 127. Trust the functions over the looseness.

Floats have no pair, for two reasons that are worth stating rather than guessing at. f64’s finite bounds cannot be written as vilan literals at all — there is no exponent syntax, so 1.7976931348623157e308 is a parse error — and min_value() would have to silently pick between the most-negative finite (Rust’s f64::MIN) and the smallest positive normal (C’s DBL_MIN). That is a choice the eventual f64::MIN should make deliberately, not one this stopgap should prejudge. BigInt has no bounds by construction.

Methods

Integers (per type; shown for i32):

impl i32 {
	fun abs(self): i32
	fun pow(self, exponent: i32): i32
	fun min(self, other: i32): i32
	fun max(self, other: i32): i32
	fun rem(self, m: i32): i32     // the % operator's method
	fun diff(self, other: i32): i32
	fun is_even(self): bool
	fun is_odd(self): bool
}

Floats add the usual math surface:

impl f64 {
	fun abs(self): f64
	fun sqrt(self): f64
	fun pow(self, exponent: f64): f64
	fun floor(self): f64
	fun ceil(self): f64
	fun round(self): f64
	fun min(self, other: f64): f64
	fun max(self, other: f64): f64
	fun clamp(self, min: f64, max: f64): f64
	fun trunc(self): f64
	fun fract(self): f64
	fun sign(self): f64
	fun lerp(self, to: f64, t: f64): f64
	fun sin(self): f64      // cos, tan, asin, acos, atan, atan2, hypot
	fun exp(self): f64      // ln, log2, log10, cbrt
	fun to_radians(self): f64  // to_degrees

	// the three a reader actually comes looking for
	fun is_nan(self): bool
	fun is_finite(self): bool
	fun is_infinite(self): bool
}

Every numeric type implements Default (zero), the operator traits, and comparison.

clamp confines a value to a range. The integers inherit it from Ord; the floats are deliberately not Ord (NaN has no place in a total order), so f64 and f32 carry their own — same recipe, same result.

fun main() {
	print(9.clamp(0, 5));       // 5   — i32, through Ord
	print(9f.clamp(0f, 5f));    // 5
	print((0f - 1f).clamp(0f, 5f));   // 0
}

Conversions: as_*

Every numeric type converts to every other with Rust-as semantics. Floats truncate toward zero; integers fold two’s-complement into the target width:

fun main() {
	print((3.9).as_i32());    // 3
	print((-1).as_u8());      // 255 — folded
	print((300).as_u8());     // 44
	let wide = 9007199254740992i53;
	print(wide.as_i32());
	print((255u8).as_f64() / 2.0);
}

Conversions on literals fold at compile time.

std::math

fun min<T: Ord>(a: T, b: T): T
fun max<T: Ord>(a: T, b: T): T
fun minmax<T: Ord>(a: T, b: T): (T, T)   // (smaller, larger)

std::random

fun range<T: Random>(low: T, high: T): T   // uniform in [low, high)
// implemented for i32, u32, f64
import std::random;

fun main() {
	let roll = random::range(1, 7);   // 1..=6
	print(roll >= 1 && roll <= 6);
}

Not cryptographic: for tokens and ids use std::crypto (random_uuid, random_bytes; see misc).