Variables and constants
Beans has two ways to bind a name: let and var. Every binding states its
type. There is no type inference for let, var, parameters, fields, or loop
variables.
let x: int = 5 // cannot be reassignedvar total: decimal = 0.0 // can be reassignedlet means the variable cannot be rebound to a new value. The object it
points to can still change inside. Beans has no borrow checker and no mut
markers.
let xs: List<int> = [1, 2, 3]xs.push(4) // fine: the list changes, the binding does not// xs = [9] // error: cannot rebind a letLiterals build values, not classes
Section titled “Literals build values, not classes”Structs and collections have literal forms:
let point: Point = Point { x: 3, y: 4 }let values: List<int> = [1, 2, 3]let counts: Map<string, int> = {"beans": 2}Classes never use field literals. Build them with new Class(...) so every
construction goes through init. See Classes.
move name moves the value out of a local binding. The old binding cannot be
read again unless it is a var that gets a new value first:
var job: Job = next_job()let running: Job = move jobjob = next_job() // reinitializes itThe checker rejects use-after-move, and rejects a value moved on only one branch (a move on every branch is fine). Normal parameters, loop variables, match bindings, and closure captures are borrowed, so they cannot be moved.
Parameters: borrow, move, inout
Section titled “Parameters: borrow, move, inout”Parameters borrow by default. A move parameter owns its argument and drops
it at function exit unless the body moves it onward:
fn enqueue(move jobs: List<Job>) { /* ... */ }
var batch: List<Job> = make_batch()enqueue(move batch) // batch is moved inA fresh result can be passed straight into a move parameter without the
keyword (enqueue(make_batch())); only an existing move-only local needs move.
Move modes must match across interface methods and overrides.
An inout parameter aliases one mutable caller local for the duration of the
call. It is not copy-in/copy-out:
fn swap(inout left: int, inout right: int) { let old: int = left left = right right = old}
var a: int = 1var b: int = 2swap(inout a, inout b)The caller must write inout, the argument must be a var, and the same local
cannot appear in two inout positions of one call. An inout parameter cannot
be captured by a closure.
Move-only handles
Section titled “Move-only handles”Some values are move-only outer handles: binding, assigning, storing, or
returning them uses move, while function parameters and loop reads borrow by
default. List, Map, OrderedMap, Box<T>, and Arena<T> are move-only,
and so is any user type declared unique class:
unique class Packet { bytes: Bytes}A move-only value cannot be copied by binding, assignment, return, or storage;
use move to move it. A subclass of a move-only class is move-only too. This
controls the reference handle; fields inside the object still follow their own
rules.
clone() makes an independent copy of a collection, so changing the clone does
not change the original (it needs every stored type to implement Clone).
The full ownership model (reference counting, the cycle collector, Shared,
Weak, and deinit) is covered in Memory and ownership.