Variables & References
Variables hold data temporarily in the runtime, outside the document itself, while it's being read or transformed. Declare them with let or const — const just means the binding can't be reassigned. A typed declaration casts on assignment, the same way a typed field does:
start: "Hello, "
end: "!!"
#[main]
fn main() {
let start = self.start;
const name: str = "John";
pln(start + name + self.end);
}Scopes
A new brace-delimited block creates a new scope, the same as most other languages — an inner scope can read outer variables, but disappears (along with anything it declared) once the block ends:
#[main]
fn main() {
let outer = 10;
{
let inner = 5;
pln(outer + inner);
}
pln(outer);
}Value Types
Booleans, numbers, strings, versions, and promises are value types — copied automatically whenever they're read into a variable:
value: 42
#[main]
fn main() {
let val = self.value;
val += 50;
pln(val, self.value);
}val and self.value are independent after that copy — changing one doesn't touch the other. The & operator opts a value type into reference semantics instead:
value: 42
#[main]
fn main() {
let val = &self.value;
val += 50;
pln(val, self.value);
}Same code, one character different — but now val is self.value, not a copy of it, so changing one changes both.
Pass by Value vs Reference
The same distinction applies at a function call. Passing a value type as a plain argument hands the function a copy; passing it with & hands it the original:
fn push_name(name_str: str, name: str) {
name_str.push(name);
}
#[main]
fn main() {
let s = "Hello, ";
self.push_name(s, "John");
pln(s);
}fn push_name(name_str: str, name: str) {
name_str.push(name);
}
#[main]
fn main() {
let s = "Hello, ";
self.push_name(&s, "John");
pln(s);
}Identical function, identical call site — the only difference is the & at the call, and it's the difference between s staying "Hello, " and coming back "Hello, John".
Reference Types
Tuples, maps, sets, lists, and blobs are reference types — copied by reference automatically, with no & needed:
value: [1, 2]
#[main]
fn main() {
self.value.push_back(3);
self.value.push_back(4);
pln(self.value);
}Nodes, data pointers, and function pointers are technically value types, but behave like reference types in practice — they just point into the document's graph rather than holding a copy of it, so passing one around is always cheap. This is the same flat-pointer structure from The Document Graph.
Call by Reference
Indexing supports references too — &list[i] is shorthand for &list.at(i):
#[main]
fn main() {
const list = [1, 1, 1, 1];
let val = &list[1];
val += 99;
let last = &list.back();
last += 99;
pln(list);
}const on list only blocks reassigning the list binding itself — mutating what it points to, through a reference, is unaffected.
Loop by Reference
for...in can bind by reference too, since it's calling at internally on each iteration:
#[main]
fn main() {
const list = [1, 2, 3];
for (let val in &list) val += 5;
pln(list);
}