Types
Primitives
| Type | Meaning |
|---|---|
i8 i16 i32 i64 | signed integers |
u8 u16 u32 u64 | unsigned integers |
f32 f64 | IEEE floats |
bool | true / false |
char | one Unicode scalar value |
string | immutable UTF-8 text, value semantics |
OsString | bytes that need not be UTF-8, a name the operating system gave, value semantics |
() | unit; void is its spelling in a return position |
never | the type of an expression that does not return (panic, return, break) |
All primitives except string are Copy. string is cheap-clone: the compiler may copy it implicitly, because a copy is one retain.
The bootstrap subset (Core v0) implements i32 i64 u8 f64 bool char string () never.
Literals
| Literal | Examples | Type |
|---|---|---|
| Integer | 42, 1_000_000, 0xFF, 0b1010 | i64 unless the context requires another integer type |
| Float | 3.14, 1e-9, 2.5e3 | f64 unless the context requires f32 |
| String | "text", escapes \n \t \r \\ \" \0 \u{1F600} | string |
| Template | `x = ${expr}` | string |
| Char | 'a', '\n', '\u{41}' | char |
| Boolean | true, false | bool |
| Unit | () | () |
There is no null literal: absence of a value is None. An integer literal is read as 64 bits and checked against the type it is written at, so a value that does not fit is refused rather than truncated.
Arithmetic
Operands of an operator have the same type: there is no implicit widening between integer types, or between integers and floats. i64 + i32 is an error; write a + (b as i64). Integer division truncates toward zero. Overflow panics, and a build with --release wraps instead.
Conversions
as converts between numeric types, from char to an integer, and from bool to an integer. Anything else is an error. An integer becomes a char through char.of(n), which answers None for a number that is not a Unicode scalar value:
fn main(): i32 {
let big: i64 = 300;
let low = big as u8; // 44: the low eight bits
let code = 'A' as i64; // 65
let half = 7 as f64 / 2.0; // 3.5
let letter = match char.of(66) { Some(c) => c, None => '?' };
println(`${low} ${code} ${half} ${letter}`);
0
}Composites
Array<T> // growable array, owned
Option<T> // Some(T) | None
Result<T, E> // Ok(T) | Err(E)
Box<T> // owned heap value
shared T // a counted allocation: `.clone()` retains, `*r` reads, read-only
dyn Trait // any type implementing Trait, method chosen at run time
Map<K, V> // hash map, from std
(A, B) // tuple; fields t.0, t.1
fn(A): R // function valueT? is sugar for Option<T>, allowed anywhere a type is written. Generic arguments nest without limit: Option<Array<Token>>, Map<string, Array<i64>>.
A tuple type has at most 16 elements. A tuple is laid out as a struct with one field per element.
What copies, what moves
Three classes decide everything about assignment and calls:
- Copy: every primitive except
string, tuples of Copy types, and structs or variants whose parts are all Copy. Copied on assignment and call, freely. - Plain: Copy, plus
string, plusOption,Result, tuples, structs and variants made only of plain types. Copied implicitly where a copy is what the program needs; moved instead when the copy would be the last use. - Everything else (
Array,Box,shared,Map,fn, and any type containing one): moved. See ownership.
Floats: the methods of f64
f64 has Rust's methods: a root, a power or a logarithm is a method on the value, a limit is a static on the type, and the mathematical constants are in std.math:
let area = PI * r.powi(2); // PI from std.math, powi a method of f64The whole list, and what a float answers at its edges, is on Math.
Nominal, not structural
Two structs with the same fields are different types. A type alias (type Id = i64;) is a new name, not a new type.