Bindings
const MAX: i64 = 1024; // compile-time constant, module level
let x = 1; // a local
let y: f64 = 2.0;
let n = 0; // the same binding: a local that is written is mutable
n = n + 1;
n += 1;Mutability is inferred, and let is the one binding keyword
Whether a name can be reassigned, and whether the value it holds can be mutated in place (p.x = 1, xs.push(v), x.bump()), follows from what the body does with it: a local that is written is mutable, and one that is not is not. let declares a local, and there is no second spelling to write the intent down, because an annotation the compiler can compute is not asked for.
let x = 1;
x = 2; // accepted: `x` is written, so `x` is mutable
let xs: Array<i64> = [];
xs.push(1); // a method that edits its receiver needs a place, and a binding is oneWhat mutability is not:
- A
constis not a local and is never assignable. - A place reached through a view is read-only, whatever the binding says.
- A parameter follows its mode: a parameter the clause names in neither
editsnortakesis read-only, a parameter the clause saysedits(edits selfon a method) is writable, and a plain parameter that the body writes is anedits, written or inferred from the body. - A value that was moved or taken cannot be written through the old name.
- A place reached through a
sharedis read-only, because two tasks could write it at once. The place a lock lends, aLocked<T>, is writable for the length of one call: see concurrency.
A binding that nothing writes is accepted: the mutability follows from the body, and an unused local is the unused-declaration analysis' concern.
const: computed when the program is compiled
const lives at module level and its value is computed at compile time, so what every use reads is a literal however the initializer was written.
const fn buckets(bits: i64): i64 {
let base = 1 << bits;
if base > 8 { base } else { 8 }
}
const BUCKETS: i64 = buckets(12); // 4096, computed here
const MASK: i64 = BUCKETS - 1; // a constant may mention another
const READY: bool = BUCKETS > 100;
fn main(): i32 {
let n = const { buckets(4) * 2 }; // computed where a program needs one
n as i32
}The rules:
- A
const fnis a function the compiler can run at compile time. It is an ordinary function too: calling it at run time changes nothing. - What the evaluator holds is what a value can be at compile time and nothing that allocates: integers, bools, chars, and string literals. Arithmetic, comparison, the logical operators,
as,if,let,return, a block's tail value, another constant by name, and a call to aconst fn. const { expr }computes a value where a program needs one.- A
const fnwhose body holds anything else - a loop, an index, an assignment to a local, amatch, a literal that allocates, a method call, a call to a function that is not aconst fn, a closure, a template - is refused at its declaration, whether or not anything calls it. - Evaluation at compile time has a bounded room, so a
const fnthat recurses without end is an error when the program is compiled rather than a compiler that hangs. - A constant that overflows, divides by zero, or shifts past the width of its type is an error at compile time: compile time has no panic to fall back on.
- There are no const generics:
Array<T>is dynamic, so nothing in the language has a size to parameterize.
Conventions
Not enforced, but the whole tree follows them: snake_case for functions, variables and modules; PascalCase for types, traits and the cases of a variant; SCREAMING_SNAKE for constants.