Skip to main content
Version: 0.10.0

Tutorial: References

This tutorial introduces Metel's reference model. References are explicit aliases to existing storage:

  • &x creates a shared reference with type &T
  • &var x creates an exclusive mutable reference with type &var T
  • &var also 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.

tip

Read This As Two Choices

Choose reference capability at the address-taking site:

  • &x for &T
  • &var x for &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.

note

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.

caution

Boundary Rule

References are for explicit aliasing. If a feature depends on exactly-once ownership, regular references are the wrong tool.

What you learned

  • &x yields &T.
  • &var x yields &var T, and &var also works on addressable lvalue paths.
  • Writing to a &var T writes through to the referenced storage.
  • Reading a plain value out of a reference is type-directed; there is no *p operator.
  • Fields, methods, and function-reference calls auto-dereference through reference layers.

Next: Closures and Capturing — first-class functions, captured values, and shared mutation.