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.
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
There is no explicit dereference operator. A value is read out of a reference when the expected type is the referent type:
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
}
A let binding that stores a &var T can still write through the reference. The binding itself is not being reassigned; the storage behind the reference is being updated.
Explicit Where It Matters
Metel makes aliasing visible with & and &var, but ordinary field access and method calls do not require a separate dereference operator.
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 to a
&var Twrites through to the referenced storage. - Reading a plain value out of a reference is type-directed; there is no
*poperator. - Fields, methods, and function-reference calls auto-dereference through reference layers.
Next: Closures and Capturing — first-class functions, captured values, and shared mutation.