Borrowing and References
Read a resource without moving it, and write to it when necessary.
8.1 Read and write borrows
&x is a read borrow; &mut x is a borrow for mutation. Use the same markers on function parameters.
import std::prelude::Affine
fn length(&v: Vec(i64)): u64 {
Vec::len(&v)
}
fn append_one(&mut v: Vec(i64)): () {
Vec::push(&mut v, 1)
}
var v := Vec::new()
append_one(&mut v)
let n := length(&v)
perform show("len", n)
// Read and mutate the same vector through separate temporary borrows.
・ length ・ append_one len = 1
len = 1
length does not consume the Vec; append_one changes the existing vector. Only a var can be borrowed with &mut.
8.2 Views that return borrows
A function returning a borrow lets you refer to part of a large value without copying it.
import std::prelude::Affine
fn first(&v: Vec(i64)): &i64 ! {out_of_bounds} {
Vec::index(&v, 0)
}
var values := Vec::new()
Vec::push(&mut values, 7)
let head := handle {
let &r := first(&values)
r
} with {
out_of_bounds() => 0
}
perform show("head", head)
// Return a view of the first element and handle an empty vector.
・ first head = 7
head = 7
first returns a borrow of the first element. The possibility that no such element exists is expressed by the out_of_bounds effect; ! {out_of_bounds} is the effect row. If this effect is not handled, execution stops with an “unhandled effect” error. Here the handler returns 0, providing a fallback for an empty vector.
8.3 Pay attention to borrow scope
Tauto imposes strong restrictions on borrows. They cannot be placed in arbitrary structs or captured by closures that escape. With a borrow bound by let &r := ..., the owner is frozen until the end of the current block. Freezing is lexical; it does not end at the borrow's last use.
There is an exception for borrows of duplicable values such as &i64: these are treated like the i64 value itself and do not freeze the owner. Use a borrow of a nonduplicable element to see freezing in action.
import std::prelude::Affine
fn first_row(&m: Vec(Vec(i64))): &Vec(i64) ! {out_of_bounds} {
Vec::index(&m, 0)
}
var m := Vec::new()
var row := Vec::new()
Vec::push(&mut row, 1)
Vec::push(&mut m, row)
let n := handle {
let &r := first_row(&m)
Vec::push(&mut m, Vec::new())
Vec::len(&r)
} with {
out_of_bounds() => 0
}
perform show("n", n)
// This deliberately mutates an owner while its nonduplicable element is borrowed.
・ first_row error 14:13: m is frozen by r
The program attempts to change m inside the block where r remains alive, so checking rejects it.
ch08-freeze.taut:14:12: use error: m is frozen by r
Move Vec::push(&mut m, Vec::new()) outside the handle, after the borrow's block, and it is accepted.
This is more conservative than ending a freeze at the last line that uses a borrow. In exchange, borrow boundaries can be read directly from the source.
Exercise 8.1: Why does fn first(&v: Vec(i64)): &i64 ... need out_of_bounds?