C+
Language · View as Markdown
v0.0.27 is a macOS / AppKit release. That is the supported path. Other platforms are not recommended; wait for a later version.

Variables, mutability, and scope

let x: i32 = 5;             // immutable
let y = 5;                  // inferred as i32
var z: i32 = 0;             // mutable
z = 7;

let w: i32;                 // uninitialised
w = 12;                     // first write counts as init; later writes need `var`

Mutability is opt-in: a plain let is immutable and freezes the whole value (a C+ struct is a value type, so let p; p.x = 1 is rejected), and you reach for var only when you need it. If you forget to initialise on a path, the compiler tells you (E0345). If you reassign without var, it tells you (E0305). Shadowing is allowed across scopes, but same-scope shadowing is rejected (E0363): a new let with the same name in an inner block introduces a new binding.

Scope is curly-brace lexical. A binding lives until its enclosing block exits, at which point its Drop runs.

A let _ = expr; is a discard binding: the expression is evaluated and its value is dropped immediately. Use it to run something for its effect, or to consume an owned value you do not need, without introducing a name.

let _ = make_buffer();      // evaluated, then dropped right here

Destructuring a struct

A let or var binding can destructure a struct, moving each named field into its own binding:

struct Pair { a: i32, b: i32 }

fn sum(take p: Pair) -> i32 {
    let Pair { a, b } = p;      // a and b are bindings now; p is consumed
    return a + b;
}

Write var Type { ... } to make the new bindings mutable. The field list must be exhaustive — name every field — and the binding moves each field out, so destructuring is sound in exactly the places a hand-written field move is. A struct with an explicit drop cannot be destructured (E0509): that would move its fields out from under the destructor.

Module-scope const and static

let lives inside a function. For named values shared across functions, or for the C-style "static storage" pattern where a value lives for the whole program, use const or static at module scope.

// `const` — a typed alias for a literal. No storage, no address.
// Every use site is rewritten to the literal at compile time.
const HEADER_BYTES: usize = 176;
const PI: f32 = 3.14159f32;

// `static` — a global with a real address, initialised once before main.
// An immutable form (declared `let` at module scope) would live in .rodata;
// a `static` is the addressable, mutable, foreign-facing global.
static IMMUTABLE_OFFSET: i32 = 50;

// `static` — mutable, addressable global. Access is bare: the `static`
// keyword is itself the marker. Cross-thread safety of a shared `static`
// is the developer's responsibility, since nothing proves absence of data races.
static COUNTER: i32 = 0;

fn bump(by: i32) {
    COUNTER = COUNTER + by;
    return;
}

Three rules:

  1. A const (or scalar static) initializer may be any pure expression over literals and other consts, folded before type-check. Evaluation is typed at the declared width: overflow, a bad shift, division by zero, mixed types without a cast, and a reference cycle are E0921. The wrap spellings +% -% *% wrap exactly as at runtime. Cross-const references resolve in any order.

    const MASK: u64 = (1u64 << 40) - 1u64;
    const CAP: usize = 1024;
    const CAP2: usize = CAP * 2;
    

    A static additionally accepts an array literal or fill (static Z: [u8; 64] = [0u8; 64];) and a non-generic struct literal (static S: Point = Point { x: 1, y: 2 };), composing recursively, since it becomes an LLVM constant aggregate.

  2. A type annotation is required; there is no inference. const FOO = 5; is rejected.

  3. static reads and writes are bare — the static keyword is the marker, so no wrapper is needed. Cross-thread safety of a shared static is the developer's responsibility, since nothing proves absence of data races.

You want Use
A named literal referenced at multiple sites const
A fixed offset or lookup table read at runtime static
A mutable counter, RNG state, or lazy cache static