Tutorial: References
This tutorial introduces Metel's reference model. References are explicit aliases to existing storage:
&xcreates a shared reference with type&T&var xcreates an exclusive mutable reference with type&var T&varalso works on addressable fields, elements, and nested lvalue paths- references are storable values, but they do not own the thing they point at
The file is still named pointers.mdx for link stability, but the current language surface calls these values references.
Taking a reference
fun main() {
let value = 42;
let read_ref: &i64 = &value;
let current: i64 = read_ref;
println(current); // 42
var counter = 0;
let write_ref: &var i64 = &var counter;
*write_ref = 50;
println(counter); // 50
}
&i64 is a shared reference to an i64. &var i64 is an exclusive mutable reference to an i64. Writing through a reference uses the explicit dereference operator *write_ref = ... — plain assignment to the binding itself is rejected, since the binding is not what's being updated.
Read This As Two Choices
Choose reference capability at the address-taking site:
&xfor&T&var xfor&var T
Why references exist
References are mainly about explicit aliasing. They let multiple places talk about the same storage instead of copying a value around.
That matters in patterns like:
- mutable shared state between closures
- data structures that need indirection
- APIs that observe or update a value without taking ownership of it
Shared mutable state
References are useful when multiple parts of a program need to talk about the same value explicitly. That includes shared mutable state between closures:
fun main() {
var total = 0;
let cell: &var i64 = &var total;
let add_one = () -> {
*cell += 1;
};
let add_ten = () -> {
*cell += 10;
};
add_one();
add_ten();
println(total); // 11
}
Both closures mutate the same underlying i64 through the same reference.
Ordinary closure capture is by value. The reference is what makes the sharing explicit: both closures talk to the same storage location, not independent copies.
Taking mutable references to fields and elements
&var is not limited to a plain variable. It also works on addressable paths such as struct fields, tuple elements, and array elements:
struct Counter {
value: i64,
}
fun main() {
var counter = Counter { value = 10 };
let field_ref: &var i64 = &var counter.value;
*field_ref += 5;
println(counter.value); // 15
}
That same rule applies to nested paths. The write goes back to the original storage location, not a temporary copy.
Reading and writing through references
Reading is implicit: a value is copied out of a reference whenever the expected type is the referent type, with no operator needed. Writing is explicit: it always goes through the dereference operator, *r = ...:
fun main() {
var n = 1;
let r: &var i64 = &var n;
*r = 4; // write-through
let value: i64 = r; // type-directed read
println(value); // 4
}
*r = 4 writes through the reference to the storage it points at. Plain r = 4 is rejected — the binding itself isn't reassignable, and reassigning it wouldn't be the write you want anyway. Reading has no such operator: let value: i64 = r copies the referent out because the expected type (i64) is already known at that position.
Explicit Where It Matters
Metel makes aliasing visible with & and &var. Ordinary field access and method calls auto-dereference without needing *, and reading a plain value out of a reference is implicit too — but writing through a reference always requires *r = ....
Auto-deref for fields and methods
Field access and method calls automatically dereference through reference layers:
struct Counter {
value: i64,
}
extend Counter {
fun increment(&var self) {
self.value += 1;
}
}
fun main() {
var counter = Counter { value = 0 };
let r: &var Counter = &var counter;
r.increment();
println(r.value); // 1
}
Auto-deref chains through multiple reference layers, so a &&var Counter can still reach the underlying Counter for field reads and method calls.
Function references
References to closures or functions can be called directly:
fun main() {
let f = () -> i64 { return 42; };
let r: &() -> i64 = &f;
println(r()); // 42
}
References and future ownership features
Current references provide explicit aliasing for non-linear values. Future affine/linear ownership features will introduce stricter rules for exactly-once consumption; ordinary &T / &var T references are not the mechanism for consuming owned values.
Boundary Rule
References are for explicit aliasing. If a feature depends on exactly-once ownership, regular references are the wrong tool.
What you learned
&xyields&T.&var xyields&var T, and&varalso works on addressable lvalue paths.- Writing through a
&var Trequires the explicit dereference operator:*r = value. - Reading a plain value out of a reference is implicit and type-directed; no operator needed.
- Fields, methods, and function-reference calls auto-dereference through reference layers.
Next: Closures and Capturing — first-class functions, captured values, and shared mutation.