Cone Language Reference

Borrowed references: lending a value

On this page

Moving a value just to use it, and then moving it back, would make every function awkward. A borrowed reference lends a value instead: the function uses it for as long as it needs, and the owner keeps it. A borrow is the cheapest reference there is, it works on a value whatever manages its memory, and the compiler makes sure it never outlives what it borrowed. This page covers making a borrow, using one, how long it may live, and what it leaves its source free to do. Afterwards you can write functions that read and change values they do not own:

mod tally;

import stdio use *;

struct Tally {
  pub yes i32;
  pub no i32;
}

// &mut: a borrow that may change the tally
fn vote(t &mut Tally, yes bool) {
  if yes { t.yes += 1; } else { t.no += 1; }
}

// &: a borrow that may only read it
fn report(t &Tally) {
  print <- t.yes, " yes, ", t.no, " no\n";
}

fn main() {
  var hall = new Tally(0, 0);          // a local variable
  let online = new Uni[Tally](0, 0);    // a single owner

  vote(&mut hall, true);               // lend hall, mutably, for the call
  vote(&mut hall, true);
  vote(&mut hall, false);
  vote(online, false);                 // an owner lends what it owns the same way

  report(&hall);
  report(online);

  let ayes = &hall.yes;                // a borrow of one field
  print <- "the hall's ayes: ", *ayes, "\n";
}

It prints:

2 yes, 1 no
0 yes, 1 no
the hall's ayes: 2

Try this: move the line let ayes = &hall.yes; up to just after hall is made. The compiler now refuses the first vote: 'hall' is borrowed, and that borrow is used again. It may not be borrowed mutably until after that last use. A borrow that reads, still to be used, is a promise that nothing changes underneath it.

Borrowing needs no region at all: hall is an ordinary local variable, and a global can be borrowed just as well. That is why borrowing comes first in this part. And because a borrow does not care where its value lives, report serves the local and the Uni alike, as describe served every strategy on the opening page.

Making a borrow#

A reference carries the address of a value. Through it, the value can be read, or changed, and several references can reach the same value: change it through one, and the others see the change, which a copy never would. For anything bigger than a number, passing a reference is also cheaper than copying the whole value. A borrowed reference is one made from a value that already exists, for a limited stretch of the program.

From a variable. The ampersand in front of a variable's name borrows the value it holds. It works on any variable, global, local or parameter:

let glovar = 2;
fn func(parm i32) {
  var localvar = 3;
  let ref1 = &glovar;    // ref1's type: &i32
  let ref2 = &parm;      // ref2's type: &i32
  let ref3 = &localvar;  // ref3's type: &i32
}

The * operator reaches the value: in an expression it reads it, and on the left of an assignment it changes it:

var a = 3;
let ref = &mut a;   // ref borrows a, and may change it
let val = *ref;     // val is 3
*ref = 4;           // a is now 4

From another reference. An ampersand in front of a dereference borrows from a reference, let borref = &*someref;. More often this happens by itself: wherever a borrowed reference is wanted, in a call or an assignment, and an owning reference of the same value type and a suitable permission is given, the owner lends what it owns:

fn incr(nbr &i32) i32 {
  *nbr + 1;
}

let allocref = new Uni[i32](1);   // an owning reference to an allocated 1
incr(allocref);                  // lends a borrow of the 1: gives 2

The owner is borrowed from, not moved. It is frozen while the borrow is still to be used (below), may lend again afterwards, and still frees its value at the end of its own scope. The lend is exactly the borrow written out, &*allocref, with the same rules: a call may not be handed a &uni of an owner beside another borrow of it, twoUni(a, a), nor lend the owner to a self &mut method while moving it in as the argument, a.absorb(a). An owner that is its value's only holder (a Uni reference, or an Rc one not yet shared) may lend any permission, &uni included, so fn f(p &uni Point) may be called twice with one owner. A shared owner, such as an Rc[mut, T] reference, cannot lend a &uni.

Because any reference can lend a borrowed one, functions and methods usually take borrowed references as parameters. Such a function works on values of every region and many permissions, and none of the regions' bookkeeping, counting or tracing, happens inside it.

Borrowing a field or an element#

A borrow may point inside a value: at a field of a struct, or an element of an array, to any depth. Parentheses mark where such a chain ends, where needed:

let ref1 = &apoint.x;      // a field within a struct
let ref2 = &vec[i];        // an element within an array
let ref3 = &vec[3].x;      // a field within an element
  • A reference as the source is read through. &pointref.x, where pointref is a reference to a Point, is &(*pointref).x, however many levels of reference there are (reading through references).
  • A type's own indexing operator decides. Where the indexed value's type declares a `&[]` method, that method says what a reference to one of its elements is, and is called with the borrow evaluated so far as self: let r = &mut list[key]; passes &mut list to the list's `&[]`. A plain &list[key] passes a read-only &list, which a `&[]` declared with self &mut does not accept (Error 1056: `&x[i]` hands `&[]` a read-only receiver, and this type's `&[]` takes `self &mut`). For such a type, list[key] is the read-only way to an element.
  • A method call is not part of the chain. The ampersand applies to the whole term, so in &x.method(key) the method is called first and its result borrowed, a temporary (below). To call a method on a borrow, borrow first: (&p).twice().

A plain each borrows each item to read it. To change items in place, walk a source that lends mutable references, mutItems() (Changing items in place), and a match on a borrow binds references into the matched value (Matching through a borrow, below):

each x in list.mutItems() {
  ++*x;                    // x is a mutable reference to each number in turn
}

What a borrow may do: its permission#

Every reference carries a permission, written after the ampersand, which says what may be done through it: read the value, change it, copy the reference. With none written, a borrow is read-only, ro. The usual ones are & to read, &mut to read and change (several &mut borrows of one value may exist at once, all on one thread), &imm for a value that nothing will change while the borrow lasts, and &uni for the one and only way to the value. The permission asked for must be one the source can give: a mutable borrow cannot be made from a let variable. Permissions, next, has all of them, and why they also keep threads from racing.

Using a reference#

A reference stands in for the value it points at, and most of the time it can be used as though it were the value.

Comparing#

== and != on two references compare the values they refer to, using the value type's own == (a number's, an enum's, or one a struct declares), and so do the ordering operators. A reference may also be compared with a value, on either side, when the value copies freely (a number, a bool, a char, a plain struct): the borrow is read through, as it is for any operator. A reference to a type that moves, such as a String or a struct with a finalizer, is not read through against a value, since that would take the value out of the borrow. A type with no == (or no ordering) cannot be compared through a reference either. Two slices compare their elements one by one. Not built yet: comparing what two virtual references refer to.

To ask instead whether two references point at the same place, use the identity operator ===, or its negation !==. Identity reads nothing through either reference, so it works whatever the value type, even for an opaq reference. Both references must have the same type. Two slices are identical when they view the same elements with the same length:

var a = 5;
var b = 5;
let ref1 = &a;
let ref2 = &b;
ref1 == ref2       // true: both refer to a 5
ref1 == 5          // true: a reference against a value is read through
4 < ref2           // true: and so is a value against a reference
ref1 === ref2      // false: a and b are different variables
ref1 === &a        // true

References have no order of their own. A pointer does: its comparison operators work on the pointer itself, never on what it points to, and on a pointer === means the same as ==.

Reading through references#

Since a reference stands in for its value, most uses read through it without a *. A field, a method called by name, or an index reaches through every level of reference: a reference to a reference (&&Pt, &mut &Pt), an owner behind a borrow (&Uni[Pt]), a slice or a virtual reference behind one. That is what the closure of iter().filter(...) relies on, since it is handed a borrow of the borrowed item:

let y = ref.y;                                       // (*ref).y
let big = pts.iter().filter(p => p.x > 5).count();   // p is &&Pt: p.x is (**p).x
let n = rr.len();                                    // rr is &&&Pt: (***rr).len()

The outermost level wins a name. A method the outer reference or owner itself declares is used before the receiver is read through any further: === on two &&Pt asks whether the outer references are the same place, and a struct that holds a reference and declares its own len has that one called, never the held value's. For a method, if one accepts the reference itself it is used; if not, the reference is read through first. A raw pointer is never read through: its own operators are used, and it is dereferenced with * (Pointers).

The access is exactly what the dereferences written out would give, and each level keeps its own permission. Through &mut &Pt a write r.x = 9 is refused, as it is written out, since the inner level only reads. An owner reached through a & lends only &: through r &Uni[Pt], r.x = 9 is refused (Error 1017: You do not have permission to modify lval) and so is a self &mut method (Error 1034: Cannot lend `&mut` of this owner: it is reached through a reference that only reads).

An operator's operands are read through the same way. A reference on either side of an operator, or on the right of a compound assignment, gives the operator the value it refers to, so a borrowed number takes part in arithmetic and comparison like the number:

let total = sum + ref;     // sum + *ref
sum += ref;                // sum += *ref
let bigger = ref > 3;      // *ref > 3
let same = ref == other;   // two references: the values are compared, not the places

This reads a copy of a value that copies freely: a number, a bool, a char, or a plain struct with no finalizer. It never moves a value out of a borrow, so a borrowed String (or any type that moves) is not read through this way, and keeps whatever its own operators accept. Every level of borrow is read through, as a field or a method called by name reaches through every level: the closure of iter().filter(p => p > 4) holds a borrow of a borrowed number, and p > 4, p == 5 and p + 1 read it as **p would. So does the value a <- is given: print <- n prints the number a borrowed n lends, where no overload of the <- takes the borrow itself. As with a method, an operator that accepts the reference itself is used before any reading through is tried. The * stays wherever it means more than reading: an explicit copy (let y = *x), a store through the reference (*x = 4), or a function's argument.

Methods that take a reference#

A method's self is usually a reference, and the method belongs to the value type: a method on a reference to a Point is declared among Point's methods, with the reference's permission after self:

struct Point {
  pub x f64;
  pub y f64;
  pub fn scale(self &mut, factor f64) { x *= factor; y *= factor; }
}

var p = new Point(1d, 2d);
let r = &mut p;
r.scale(3d);               // p is now 3, 6

A method may also ask for the owner of its type: a borrow of an owner, written self &Uni[Self], self &Rc[Self], self &Arc[Self], or the borrow of any other owning reference to the type. Such a method is found only through an owner of that kind, which is borrowed for the call and is still the caller's afterwards; a plain value, a borrow, or an owner of another kind has no such method, just as for any method that does not accept the receiver. It is tried after the ordinary selection, so a type's other methods are unaffected. Inside the method, self reads through the owner to the value, so self.n is the field, and the owner itself is what the method can hand on or inspect, as str's cstr() does, which needs the owner's zero byte after the text (C strings):

struct Node {
  n i64;
  pub fn viaUni(self &Uni[Node]) i64 { self.n + 1; }   // only a Uni[Node] has this method
  pub fn plain(self &) i64 { n; }                    // any Node, however held
}

let a = new Uni[Node](10);
let x = a.viaUni();                // 11; the Uni is borrowed, and a is still ours
let y = new Node(10).viaUni();     // Error 1056: No method declared by `viaUni` accepts the call's arguments

A reference that may be missing#

A reference always points at a valid value. One that may be missing is an Option of a reference, written with a question mark, ?&imm i32, and holds either a reference or None. Its value can only be reached by first binding the reference it holds, which a missing one never gives:

let maybePoint ?&imm i32 = Some(&imm x);
if let point Some = maybePoint {   // bound only if maybePoint holds a reference
  let p = *point;
}
let point2 = *maybePoint;          // Error 1031: May only de-reference a simple reference or pointer.

An optional reference takes no more room than a plain one: None is an address no value can have (typically 0), so no separate tag is needed.

Slices and virtual references#

Two kinds of reference carry more than an address. A slice, &Array[T], points at a run of elements of one type and carries their number; it may be indexed, and every index is checked against that number. A virtual reference gives an abstract view of a value of one of several types: beside the value's address it carries the information that finds the right field or method at run time.

How long a borrow may live#

A borrow is only safe while its value exists. Here is code that would read a value after it is gone:

fn getval() i32 {
  var ref &i32;
  {
    let a = 5;     // Error 1168: 'a', declared here, goes out of scope while 'ref' still
    ref = &a;      //   holds a borrow of it, used again below.
  }
  *ref;            // a no longer exists here
}

a lasts only as long as the inner block, but the borrow in ref is used after it, when a is gone, to read memory that may hold something else by then. The compiler refuses it. Note what the error points at: the store ref = &a is not itself wrong. A variable's lifetime is that of whatever it holds now, so it may hold borrows of different lifetimes over its life, and had ref been used only inside the inner block, the function would be fine. What is refused is using a borrow after its source is gone, returning it from the function that owns the source, or storing it where it would outlive its source.

So every borrow has a lifetime, how long it may be used, and the compiler works it out from where the borrow came from:

  • From a global variable or a function: the whole program, the global lifetime, written 'static. Such a borrow can last as long as it likes.
  • Passed in as a parameter: it comes from the caller, so it outlives the function and may be returned, but it may not be stored in a global, which may outlive the caller. Where the signature names lifetimes, it may be returned only as one it is written with, or one the signature orders shorter (Lifetime annotations).
  • From a local variable: the block it was borrowed in. A parameter passed by value, self taken by value, and whatever an owner passed by value owns belong to the function, as its locals do: a borrow of them may be used anywhere in the function, but never returned or stored outside it.
  • Through another borrowed reference: the lifetime of what that reference points at, not of the variable holding it. So a field of what a borrowed parameter points at may be borrowed and returned, even through a local the parameter was copied into, a var one too, so long as it holds nothing shorter-lived.

A value chosen by an if, a match or a block has the lifetime of the shortest-lived value it could give. A borrowed reference may be stored in a place that outlives the function (a global, or what a parameter points at) only when its own lifetime is at least as long as that place's.

A borrow is followed wherever it is held. A value that contains one, in a struct's field, an Option or an Rc, has the lifetime of that borrow, whatever its own type, so returning it, or storing it where it would outlive what it borrows, is refused:

struct R { pub n i64; }
struct H { pub r &R; }

fn inStruct() H {
  let x = new Uni[R](4);
  new H(&*x);              // Error 1064: Returned value carries a borrow of 'x', which it
}                          //   would outlive: 'x' belongs to this function.
fn inOption() Option[&R] {
  let x = new Uni[R](4);
  Some[&R](&*x);           // Error 1064, the same
}
fn inRc() Rc[H] {
  let x = new Uni[R](4);
  new Rc[H](new H(&*x));   // Error 1064, the same
}

The lifetime is followed across calls too. A borrow returned through a call keeps the lifetime of what the call was handed, and a borrow handed to a call that could store it somewhere outliving the function (through a &mut parameter, say) is refused, as is one the call stores into a local that is used after the borrow's source ends:

struct Holder {
  pub r &i32;
  pub fn set(self &mut, r &i32) { self.r = r; }
}
fn id(p &i32) &i32 { p; }

fn throughCall() &i32 {
  let a = 5;
  id(&a);                // Error 1064: Returned borrowed reference outlives the local value it points to
}
fn viaParam(h &mut Holder) {
  let x = 1;
  h.set(&x);             // Error 1085: Call could store a borrowed reference where it would outlive
}                        //   the value it points to
fn refstore(slot &mut &i32, v &i32) { *slot = v; }
fn intoLocal() i32 {
  let i = 5;
  var a = &i;
  {
    let b = 10;          // Error 1168: 'b', declared here, goes out of scope while 'a' still holds
    refstore(&mut a, &b);//   a borrow of it
  }
  *a;
}

Almost always the compiler has all it needs to work lifetimes out. Where a function's signature leaves it guessing, it asks for lifetime annotations.

Borrowing a temporary#

A temporary, a value made by a call or a literal that no variable holds, dies at the end of the statement that made it (finalizers). Where a variable's initializer borrows one, it lives longer instead, to the end of the variable's block, so the borrow may be used for as long as the variable can be:

let r = &make();          // the temporary lives to the end of the block
let n = &make().count;    // so does this one, of which n borrows a field
let o &R = makeOwner();   // and an owner lent as a borrow
let t = (&make(), 1);     // and one borrowed in a tuple, array or variant literal
let u = if c { &make(); } else { &other(); };  // and one an arm's value borrows

An if's or a match's arm, and a block, whose value is in such a place extends what its value borrows the same way: only the arm that runs makes its temporary, which then lives to the end of the variable's block. Nothing else extends a temporary: not one passed to a function, a method's receiver, or one in a statement of an arm before its value. Any other temporary may be borrowed too, and lives to the end of its statement: passed to a function (show(&make())), or as a method's receiver taken by reference (make().count()). A variable holding a borrow of such a temporary may not be used after its statement, nor may the borrow be returned or stored away:

show(&make());              // the temporary lives to the end of this statement
let p = pick(makeOwner());   // Error 1168: The temporary made here ends with its statement while
p.count;                     //   'p' still holds a borrow of it, used again here.

What a borrow leaves its source free to do#

A borrow takes some of its source's rights with it. To keep the borrow's promises, the source is frozen, partly or wholly, while the borrow is still to be used. How much is frozen depends on the borrow's permission, and on how the source is reached.

A source reached as uni, that is, a local variable, which the stack alone holds, a field of one, or what a uni reference points at, has no other reference to it. So a borrow may be given more than the source can share, and the source waits until it is given back (From uni):

  • While a read-only borrow (&, &ro, &imm) is still to be used, its source may be read, and borrowed read-only again, but not changed, moved or ended.
  • While an exclusive borrow (&uni, &mut1) is still to be used, its source may not be touched at all except through the borrow: not read, borrowed, changed, moved or ended.
  • A mutable borrow (&mut) is an alias loan, as a borrow of a source reached through a shared path is (below). mut is shared and mutable, so the source may be read, changed and borrowed again while the borrow is still to be used, as one more alias of the value, and any number of &mut borrows of it may be held at once, in one call, in locals and in structs. What the source cannot do while another reference may change it is refused: being moved, sent, lent &uni or &imm, ended, or replaced whole (its old value finalized). A change of its shape, a collection reshaped or an enum given another variant, while a borrow into it is still to be used is refused too (Borrowing into a collection or an enum).
  • An &opaq borrow, which reads and writes nothing, only keeps its source from being moved, replaced whole or ended.
fn freeze(n i32) i32 {
  var k = n;
  let r = &mut k;
  *r = 5;
  let m = k;         // Ok: r is not used again
  let s = &k;
  let t = k;         // Ok: a read-only borrow still lets k be read
  k = 6;             // Error 1168: 'k' is borrowed, and that borrow is used again below.
  *s + m + t;        //   It may not be changed until after that last use.
}

fn aliases(n i32) i32 {
  var k = n;
  let a = &mut k;
  let b = &mut k;    // Ok: a 'mut' borrow of a local is an alias loan, so several may be held
  *a = 5;
  k = *b + 1;        // Ok: k is one more alias of the value
  *a + *b;
}

A source reached through a shared path, through a mut or ro reference (&mut *p, or &p.x for a parameter p &mut Point), through an Rc[mut, T] or other owner that may be copied, or a field reached through one, may already be read and changed by other references, since mut is shared. Freezing it against reading or writing would protect nothing, so a &mut or &ro borrow of it only needs it to stay alive:

  • The owner it is reached through may not be moved, replaced, changed where it is held, or ended, and may not be borrowed mutably where it is held.
  • It may not be borrowed &uni or &imm, which promise what the live borrow could break. (A shared path cannot lend either today in any case: an owner held in a field of a &mut parameter's value or of an Rc[mut, T]'s, h.app for app Uni[App], lends &mut and & and no more.)
  • An &imm borrow of it still forbids changing it, and an &opaq borrow is as above.
fn bump(p &mut Point) {
  let x = &mut p.x;
  p.x = 7;           // Ok: p's point is shared mutable, so x does not freeze it
  *x = *x + 1;
}

"Still to be used" means until the borrow's last use, not the end of its block. Once nothing will use the borrow again, the source is free, even in the middle of the block that made it. The last use may be on any path the program can take: a later pass of a loop, or the code after it, is a use too. A copy of a borrowed reference, and any value holding one (a struct, an Option, an array or a list it was put in, by building the value, by storing into it, or by a call handed a &mut of it that may store the borrow there), keeps the source frozen exactly as the borrow does, until that value's last use. A value whose type has a final method that may read the borrow uses it again when it dies. "Ended" covers the source going out of scope, being moved away (passed to a function that takes it, or assigned to another variable), and being replaced whole (assigned or swapped).

Borrowing one field of a struct freezes that field, and the whole struct with it, but leaves the struct's other fields free. Borrowing an element of an array freezes the whole array, since the compiler cannot tell one index from another. Borrowing through a borrowed reference (&mut *r) keeps frozen whatever r itself froze, while the new borrow is still to be used; what r points at is reached through a shared path, as above, unless r is uni. A reference into an enum's variant follows the rule for shape-changing values (an enum's variant).

A borrow a function returns#

A borrow returned by a function or method is a borrow of what it was handed. Every borrowed reference in a signature without lifetime annotations shares one lifetime (Lifetime annotations), so the returned borrow keeps each borrowed argument frozen, as if it were that borrow itself, until its last use. For a method, that is its receiver. Where the signature names lifetimes, it keeps frozen only the arguments whose lifetimes the result may hold, and of an argument whose own borrow's lifetime it may not hold, only what that argument holds in the lifetimes it may: a Cursor['a]'s next returning Option[&'a R] keeps frozen what the cursor holds, not the cursor. An element taken from a local list borrows the list read-only, so the list may be read while the element is used, but not pushed onto, since a push may move every element:

var list = List[Point].empty();
list.push(new Point(1, 2));
let first = list[0usize];      // borrows list, read-only
let n = list.len();            // Ok: list may still be read
list.push(new Point(3, 4));    // Error 1168: 'list' is borrowed (by '[]'), and that borrow is used
first.x;                       //   again below. It may not be borrowed mutably until after that last use.

A method returning a mutable borrow (&mut list[0usize], list.viewMut()) holds an alias loan on its receiver: the receiver may be read, lent again and written while the borrow is used, and what is refused is a change of its shape, a push, which may move what the borrow points into (Borrowing into a collection or an enum).

The arguments of one call are all used by it, so none may touch what an earlier one borrowed in a way that borrow forbids: g(&uni v, v.len()) is refused (Error 1168: 'v' is borrowed mutably for a call or value still being made, which uses that borrow. It may not be borrowed before then.), while two &mut (or a &mut and a &) of one source in one call are not: f(&mut v, &mut v), step(&w, &mut w.entities[0usize]). The borrow a method call makes of its receiver is mutable and waits for the call, meeting the arguments as a read-only borrow would, so v.push(v.len()) reads v first and then pushes, as does v.push(takeLast(&mut v)), which works the argument out before the push.

An owning reference used where a borrowed one is wanted lends what it owns, and is frozen the same way: after let b &Point = u, for u a Uni[Point], u may not be moved while b is still to be used. A container may also declare that the borrows it hands out need no loan on it, as an arena does (NoLoanMut), and a borrow carried inside a value a call returns is held like any other, such as the Option a pool's get returns (the generational Pool).

Passing a borrow on#

References are values, and can be stored and passed around like any other. Whether passing one copies it or moves it depends on its permission: most borrows copy freely, while a &uni, the one and only way to its value, moves. A &uni can also be borrowed from, which is the reborrow, and that is what happens when a &uni variable is passed where a borrowed reference is wanted (&, &imm, &mut or &uni: an argument, a method's receiver, a local's type): instead of handing the reference over, the call gets a fresh, shorter loan of it. That loan is exactly &mut *r (or &uni *r) written out: the reference is frozen as for any borrow while the loan is used, and is usable again, still unique, after the loan's last use (recovery). A result derived from the loan keeps the reference frozen while that result lives, and the callee cannot keep the loan past the caller's own storage. So a function taking r &uni Point may pass r to a &uni Point or a &mut Point parameter twice, and a method taking self &uni may call the type's self &uni, self & and self &mut methods, one after another, and then read its fields. A &uni slice is borrowed from the same way, whether it is passed to a &uni, &mut or & slice, or is the receiver of one of its own methods, so s.len() + s.len() and while i < s.len() work on a &uni slice:

fn fill(q &uni Point) { q.x = q.x + 10; }
fn twice(p &uni Point) {
  fill(p);       // fill borrows from p for the call
  fill(p);       // and p is still there, still unique, to borrow from again
  let q &uni Point = p;   // a &uni local borrows from p
  q.x = 0;
  p.y = 1;       // allowed: q's last use is behind us
}

A part of a &uni slice is borrowed from in the same way: &uni s[a..<b], &uni s[a..] and &uni s[i] are loans of part of what s reaches, so s is frozen while the sub-slice or element is used. A local holding a &uni the function was lent, such as a cursor (var cur = p, reassigned as it walks along), can be handed back, and so can a borrow from it: it holds the caller's reference, so what is borrowed from it lasts as long as that does. A borrow of the function's own value, however it came to be held in the local, cannot be handed back:

fn walk(p &uni Array[i64], k i64) &uni Array[i64] {
  var cur = p;
  var i i64 = 0;
  while i < k {
    cur = &uni cur[1..];   // a sub-slice, borrowed from the cursor
    i = i + 1;
  }
  cur;                      // the caller's slice, narrowed: allowed
}

A slice's elements are not told apart, so two parts of one slice are two borrows of the same place. One call may not be handed a &uni of an element or sub-slice beside any other borrow of the same slice (a second element, a sub-slice, the whole slice, a &, &mut or &imm), even when the indexes or ranges differ, because the call would reach the part through two paths while a unique reference promises there is one. Distinct fields of a struct are told apart. Two &mut or two & promise nothing of the kind (a &mut is an alias loan), so one call may be handed several of them, of one element or of different ones, whatever the slice or array is reached by, as it may be handed two of one field or of one local. Only a &uni is refused:

fn two(a &uni i64, b &uni i64) {}
fn byIndex(s &uni Array[i64]) {
  two(&uni s[0usize], &uni s[1usize]);   // Error 1168: '*s' is borrowed mutably for a call or value
}                                         //   still being made. It may not be borrowed as 'uni' before then.
fn byField(p &uni Pair) {
  two(&uni p.a, &uni p.b);               // Ok: distinct fields
}

Matching through a borrow: what a binding takes#

A match, a bound if and a let-else binding all take something from the value they look at: a copy, a move, or a borrow into it. Which one is decided by two things, the form of the value being matched and the form of the variant being bound, and getting it right is what lets a match change a value in place, or look at one without consuming it.

What a match takes#

The form of the expression after match decides what the match does with it, and a bound if, if let c Circle = s, reads its value the same way:

  • A place, a variable, a field of one, a dereference, an element at a name or a literal (match r), is matched as the value it holds. Every test reads it where it stands and takes nothing, so a match that only tests moves nothing away, and r is whole after it. An arm that binds is the one that takes: the binding holds the payload by value, a copy of a copy type, or a move of one that owns, which consumes r on the paths that reach that arm. A var binding changes what the arm took and never r.
  • A borrow, match &r, &imm r, &mut r, &uni r, binds a reference of that kind into r, which stays frozen to the match for as long as the bindings are used (above). That is how a match changes r: match &mut r.
  • Any other value, a call, match f(), is a temporary the match owns: it is made once, every pattern reads it, and it is finalized as the match ends, whichever case ran.

A binding takes only what it binds. Where the payload is plain data (a number, a borrowed reference, a struct of them), the arm copies it, and the place is whole after the arm, even where the enum holds an owner in another variant. A guard (case let x Some if x > 0) reads its binding before the arm is chosen, so it takes nothing from the matched value at all, a place or a call's temporary alike: it sees a copy of plain data, and a payload that owns something through a read-only borrow of it. A guard that fails leaves the value whole for the cases after it, and the arm that runs is the one that takes.

An Option shows the difference plainly. Bound from a variable, an Option[String] is moved from on the path that binds, while an else block still has it; bound from a borrow, if let text Some = &maybe, it gives an &String into the Option and takes nothing:

let maybe Option[String] = Some(String.from("kept"));
if let text Some = &maybe {      // a borrow: binds a &String, takes nothing
  print <- text.view(), "\n";
}
if let text Some = maybe {       // a place: the binding moves the String out
  print <- text.view(), "\n";
}

A let-else binding binds the same way, let n Ok = &mut r else { return 0; }; giving an &mut i32 into r; it may be var, and its else must leave (Error 1462: The 'else' of a binding like this must leave), so r is known to be the variant after the declaration, as after any branch that leaves (Binding past an early exit).

What a binding gets#

A binding gets what the variant holds. A variant declared as a type, SomeInt(i32); (variant forms), holds that type itself, with no field to name: Some, Ok and Error are declared this way. Bound by value, the binding is a copy of the i32, or, when what it holds moves, a move of it out of the matched value, which is then finalized without it; a var binding changes the copy, not the matched value. Bound through a reference (case let n SomeInt on a reference to the enum), it is a reference of the same permission to what the variant holds, an &i32 here. A variant declared as a struct binds the struct, whose tag is never a visible field.

A pattern names only the variant, bare, with no use: the match already knows which enum the value belongs to. That holds for every pattern on a value, a case, a bound if or elif, and an is test, and it looks through a reference, so case let c Circle on a &Shape names Shape.Circle. The matched value supplies the rest: the type arguments, and, where it is a reference, the kind of reference, its region and its permission. On a &imm Shape, case let c Circle binds an &imm Circle; on a &mut Shape (a borrow made at the match), an &mut Circle; on an Rc[imm, Shape], an Rc[imm, Circle]; on a Shape, a Circle. A test with no binding, case Circle or s is Circle, asks only whether the value is that variant. A pattern may still be written as the reference, case let c &Circle, which then checks that the matched value is one: it is an error where the value is no reference, or is in another region, or has a weaker permission than the pattern asks for. A managed reference is never spelled in a pattern, and the older +Rc-mut Circle is refused.

The matched value's enum is asked first. A bare name in a pattern means that enum's variant whenever the enum has one of that name, whatever the name means elsewhere in the module, so a use Colors; whose enum has a Red does not change what case is Red means on a Lamp. Only a name the enum has no variant of is looked up as any other name is. A qualified pattern, case is Colors.Red, is taken as written, and a variant named through its enum alone, case Colors.Red, means case is Colors.Red. Likewise the value supplies a generic variant's type arguments: on an Option[i32], case let s Some means Some[i32]. Arguments may still be written where the name is in scope, as Some[i32] always is; written on a variant that is in scope only because the matched enum has it, they are refused (Error 1115: Circle is a variant of the enum being matched, and that value supplies its type arguments. Drop the arguments, or name the variant through its enum).

A binding through a reference points into the variant. It is a borrow into the enum, as an element is a borrow into a list, so it is allowed through any reference, and refused only if something could change which variant the enum holds while the binding is still to be used. The full rule, with what counts as a change, is with an enum's variant. A change after the binding's last use is fine, which is how a state machine moves to its next state:

struct Machine { pub state Shape; }

fn step(m &mut Machine) {
  match &mut m.state {
    case var c Circle {
      c.radius = c.radius + 1;
      m.state = Shape.Rect(1, 1, 2, 3);   // Ok: c is not used again
    }
    else {}
  }
}

A value owned by an Rc is another owner of the same value. Where no owner can store another variant (Rc[imm, Shape]), the binding is a copy of the owner, counted one more and let go as the arm ends, typed as the variant (Rc[imm, Circle]); the variant is at its enum's address, so nothing moves. Where another owner could change the variant (Rc[mut, Shape], Rc[ro, Shape]), the narrowing is refused: bind the payload by copy, or match through a borrow of the value, which is followed. An is test binds nothing.

A struct variant bound by value is the value the arm took, under its variant's name, not a second copy of it. The arm takes the matched value into a variable of its own (a move of one that owns, which consumes a matched place on the paths to that arm), and when the arm ends it is finalized once, as the variant it is (an enum's value). Moving the bound variable, or handing it back as the case's value, moves that value. A bound variable declared var changes that value, not a matched place: assigning over it, or over one of its fields, finalizes what is replaced, and a value swapped out of it with <=> leaves what was swapped in to be finalized with it. As with a plain var local, one moved out (on every path or only on some) and then assigned over holds the new value, which is finalized with the matched value.

You can now lend values instead of moving them, write functions that serve every region at once, and read the compiler's borrow errors as the promises they protect. Every borrow above carried a permission; next is what each permission allows, and how the same few words keep two threads from racing on one value.