Cone Language Reference

Borrowing into a collection or an enum: shape changes

On this page

A list keeps its elements in one block of memory, and a push may move them all to a bigger one. A borrow of an element made before the push would point at the old block, now freed. An enum has the same problem in another form: given another variant, its bytes mean something else. Some values can change their shape, and a borrow into one must not outlive a change of it. This page covers what the compiler refuses, why, and the three ways to write code it accepts. Afterwards you can hold borrows into collections and enums with confidence, and fix the refusals when they come:

mod scores;

import stdio use *;
import collections use *;

fn main() {
  var scores = List[i32].empty();
  scores.push(10);
  scores.push(20);
  let best = &mut scores[0usize];   // a borrow into the list's block of elements
  let n = scores.len();             // fine: the list may be read
  scores.set(1usize, 5);            // fine: an element written in place
  *best = 7;                        // the borrow's last use
  scores.push(30);                  // fine: the borrow is over
  each s in scores.iter() {
    print <- *s, " ";
  }
  print <- "(", n, " before the push)\n";
}

It prints:

7 5 30 (2 before the push)

Try this: move scores.push(30); up above *best = 7;. The compiler refuses it: Error 1339: This call could change the shape of a 'List' while 'best' still holds a borrow into it ... 'push' reshapes the 'List' it is called on: it writes a field of it, runs a finalizer of an element, or moves an element out. A 'List' that changes shape may move or free what it lent.

Why a push waits for a borrow of an element#

A resizable collection (a list, a dictionary, a string) may move all of its elements to a bigger block when it grows, and an enum may be given another variant. A borrow into one points into storage that such a change would leave dangling, or read as the wrong type. The borrow itself is always allowed. What is refused is a change of shape while the borrow is still to be used, and the rule is the same for a collection and an enum.

For a local list, a read-only borrow of an element (list[i], dict[key], s.view()) holds the list against any change: it may be read, but a push waits for the borrow's last use (A borrow a function returns). A mutable borrow (&mut list[i]; list.viewMut(), a &mut Array[T] of every element; a String's s.viewMut() of its bytes) is one more alias of the elements, as in the program above: until its last use the collection may still be read and written in place, but not reshaped, by a push, a clear or a pop. Copying out what is needed before the push leaves no borrow to wait for: let firstX = l[0usize].x; l.push(p);.

A collection reached through a shared path is held to the same rule. A mut reference is shared, so another name may make the change while the borrow is in use; the borrow lets the path be read, lent and written as any alias does, and a change of the collection's shape, by the same name or another, is refused. The rest of this page is how the compiler tells.

Elements of a collection and changing its shape#

The resizable collections (List, Deque, String, Dict, OrderedDict, Set and Pool) each declare ShapeChanging (a provisional name): they may move their elements when they change, and so may a type that wraps one. A borrow of an element of a collection reached through a path other references share (a &mut parameter, an Rc[mut, T] owner, a field of self) is allowed, and refused only if something could reshape the collection while the borrow is still to be used. That has two parts, and both are enforced.

A change through the same name. A borrow that a method of the collection returns (an element, a cursor, a view), or that a free function handed a reference to the collection returns, is an alias loan of the path the collection was reached through, while the borrow is still to be used. The path may be read, lent and written, but its shape may not be changed, by a push, a clear or a store over it. Each of these is refused at the push (error 1339):

import collections use *;

struct Pt { pub x i64; pub y i64; }

fn f(l &mut List[Pt], p Pt) i64 {
  let e = l[0usize];
  l.push(p);                    // Error 1339: e is used again below
  e.x;
}

fn g(dq &mut Deque[i64]) {
  var it = dq.items();          // a cursor
  dq.push(9);                   // Error 1339: it is used again below
  it.next();
}

struct Holder {
  items List[Pt];
  fn h(self &mut, p Pt) i64 {  // the same through a field of self
    let e = items[0usize];
    items.push(p);              // Error 1339
    e.x;
  }
}

A borrow that may write (&mut l[0usize], l.viewMut()) is an alias loan the same way, and so is a read-only one. Either lets the path be read, lent again and written, and refuses only a change of its shape, by the same name or by another:

fn grow(l &mut List[Pt], p Pt) i64 {
  let e = &mut l[0usize];
  let n = l.len();              // Ok: the path may be read,
  l.set(1usize, p);             // and an element written in place
  e.x = e.x + 1;
  e.x + i64.from(n);            // (a l.push(p) anywhere above would be refused)
}

Shape-changing types of your own#

A type does not have to declare ShapeChanging to be one: the compiler finds it from the type's methods, whatever the type declares. A type changes shape when one method writes storage the type owns (a raw pointer, or a field that holds one, such as a list) and one returns a borrow. So Bag below changes shape, and so does a struct that holds a Bag:

struct Bag {
  items List[Pt];
  fn first(self &) &Pt { items[0usize]; }
  fn add(self &mut, p Pt) { items.push(p); }
}
fn bagAdd(bag &mut Bag, p Pt) i64 {
  let e = bag.first();
  bag.add(p);                   // Error 1339: e is used again below
  e.x;
}

struct Shelf {
  bag Bag;
  fn first(self &) &Pt { bag.first(); }
  fn add(self &mut, p Pt) { bag.add(p); }
}
fn shelfAdd(s &mut Shelf, p Pt) i64 {
  let e = s.first();
  s.add(p);                     // Error 1339, the same
  e.x;
}

Writing a number (a cursor that advances, a count) reshapes nothing, and neither does writing an element in place, so a type whose only writes are those does not change shape:

struct Counter {
  items List[Pt];
  at usize;
  fn current(self &) &Pt { items[at]; }
  fn advance(self &mut) { at = at + 1usize; }
}
fn count(a &mut Counter, b &mut Counter) i64 {
  let e = a.current();
  b.advance();                  // Ok: advance writes only a position
  e.x;
}

Code the compiler cannot read counts as writing: an extern function, a virtual call, a function pointer, and a function of another package whose body its include file leaves out. That is why the include file records the types its package's compile found shape-changing.

Declaring ShapeChanging only asserts what the compiler finds, so a type that declares it and has no such methods is refused:

struct Reader is ShapeChanging {   // Error 1338: Reader declares ShapeChanging, but the compiler finds
  items List[Pt];                  //   nothing in its methods that makes it so
  fn first(self &) &Pt { items[0usize]; }
}

A type of your own that is not built on a collection changes shape when a method, or a free function of its module that takes it by reference, lends a borrow of it, and one writes storage it owns. fn first(c &Cell) &i64 and fn grow(c &mut Cell) beside the struct Cell are enough. Code in another module reaches the type only through its public methods and fields:

import libc;

struct Cell { ptr *i64; }
fn first(c &Cell) &i64 { c.ptr as &i64; }
fn grow(c &mut Cell) { c.ptr = libc.realloc(c.ptr as *u8, 64usize) as *i64; }

fn cellGrow(a &mut Cell, b &mut Cell) i64 {
  let e = first(a);
  grow(b);                      // Error 1339: e is used again below
  *e;
}

When a call could reshape a collection#

A change through another name. The second part of the rule: another reference to the same collection, or a call that could make one, leaves the element dangling, so a change through it is refused too. The compiler asks of every call made while such a borrow is still to be used, or is being handed to a call, whether the call could reshape the collection. It never goes by a method's name. A call could, when:

  1. it is a method taking the collection writable whose body, or anything its body calls,
    • writes a field of the collection (clear, pop and truncate each write the length), or
    • runs a finalizer of an element, which frees what the element owns (set on a List[String] finalizes the String it replaces, so a view of that String's bytes held through another name dangles; on a List[i64], whose element has no finalizer, it reshapes nothing), or
    • moves an element that owns something out of the storage and does not put it back;
  2. it is handed a value that can reach a collection of that type (as a field, an element, a variant, a pointer, a reference or an owner) by a path it may write, and its body, or what it calls, reshapes one; or
  3. it is handed nothing that reaches one and reshapes one through a global; or
  4. the borrow points into a var global, or into anything reached from one, or is reached through a reference some var global of that type could be, and the call, whatever it is handed, reshapes one through a global or is code the compiler cannot read.

Moving elements about within the storage (swap, sortBy) destroys none and moves none out, and the bytes a String element owns stay where they are, so a view of them stays good. Writing an element that has no finalizer in place, l[i] = v on numbers, reshapes nothing.

A function whose body the compiler cannot read (an extern function, a call through a function pointer or a trait, a function of another package that is neither generic nor inline) is assumed to reshape whatever it is handed that reaches the collection, unless the call is through a virtual reference marked + ShapeKeeping. A mutable virtual reference (&<mut Trait, a &<mut fn called) is assumed to reach any collection even when handed nothing (a visitor and its closure). A read-only reference (& or &imm) cannot write what it reaches, and a value the function owns whole, a local, cannot be the collection reached through a reference, so neither makes a call one that could.

All of these are refused when the borrow is still to be used afterwards (error 1339):

fn clearOther(l &mut List[Pt], m &mut List[Pt]) i64 {
  let e = l[0usize];
  m.clear();                    // Error 1339: clear writes the length (so do pop and truncate)
  e.x;
}

fn setString(l &mut List[String], m &mut List[String], s String) usize {
  let t = l[0usize].view();
  m.set(0usize, s);             // Error 1339: set finalizes the String it replaces
  t.len;
}

fn bump(q &mut List[Pt], p Pt) { q.push(p); }

fn viaCall(l &mut List[Pt], m &mut List[Pt], p Pt) i64 {
  let e = l[0usize];
  bump(m, p);                   // Error 1339: bump is handed a list and pushes
  e.x;
}

var pool List[Pt];
fn @initpure init() { pool = List[Pt].empty(); }
fn resetPool() { pool.clear(); }

fn viaGlobal(l &mut List[Pt]) i64 {
  let e = l[0usize];
  resetPool();                  // Error 1339: resetPool clears a list through a global
  e.x;
}

extern fn elsewhere(q &mut List[Pt]);

fn viaExtern(l &mut List[Pt], m &mut List[Pt]) i64 {
  let e = l[0usize];
  elsewhere(m);                 // Error 1339: its body is not visible, so it is assumed to reshape
  e.x;
}

And these reshape nothing, so they are accepted (in a program with no var global that could be the list, below):

fn setNum(l &mut List[i64], m &mut List[i64]) i64 {
  let e = l[0usize];
  m.set(0usize, 9i64);          // Ok: a number has no finalizer
  *e;
}

fn swapStr(l &mut List[String], m &mut List[String]) usize {
  let t = l[0usize].view();
  m.swap(0usize, 1usize);       // Ok: the bytes of each String stay where they are
  t.len;
}

fn readOnly(l &mut List[Pt], m &List[Pt]) i64 {
  let e = l[0usize];
  e.x + i64.from(m.len());      // Ok: a read-only reference cannot write
}

fn ownedLocal(l &mut List[Pt], p Pt) i64 {
  let e = l[0usize];
  var mine = List[Pt].empty();
  mine.push(p);                 // Ok: a local list is not the one reached through l
  e.x + mine[0usize].x;
}

Two names for one collection can arise in several ways. Each of these is refused at the push (p is a value to push, and the push through the second name moves the element e points at):

  • A parameter. Two &mut parameters, which the caller may have made one list.
  • A field of self. A borrow of one field, and a push through other.items in a method, other being the same value.
  • An Rc[mut, T] owner. The owner copied, so two names hold the one list.
  • An arena allocation held by name. A list allocated in an arena and held by two names.
  • A reference made from a pointer. Two references made from one pointer.
struct Bag2 {
  items List[Pt];
  fn fb(self &mut, other &mut Bag2, p Pt) i64 {
    let e = items[0usize];
    other.items.push(p);        // Error 1339: other may be self
    e.x;
  }
}

fn fa(l &mut List[Pt], m &mut List[Pt], p Pt) i64 {
  let e = l[0usize];
  m.push(p);                    // Error 1339: m may be l
  e.x;
}

fn fc(p Pt) i64 {
  let l = new Rc[mut, List[Pt]](List[Pt].empty());
  let l2 = l;
  let e = l[0usize];
  l2.push(p);                   // Error 1339
  e.x;
}

fn fd(a &mut Arena, p Pt) i64 {
  let l = a.alloc(List[Pt].empty());
  let l2 = l;
  let e = l[0usize];
  l2.push(p);                   // Error 1339
  e.x;
}

fn fe(ptr *List[Pt], p Pt) i64 {
  let l = ptr as &mut List[Pt];
  let l2 = ptr as &mut List[Pt];
  let e = l[0usize];
  l2.push(p);                   // Error 1339
  e.x;
}

A borrow handed to the very call beside the reference that changes it is refused too, for the caller may have made out and x one string:

fn appendBoth(out &mut String, x &String) {
  out.append(x.view());         // Error 1339: x's view is in use while out may change shape
}

Taking the view through an element, with no name for the element, is the same as naming it first. The view of ui.items[0usize].label is of one String, the field of an Item, and a call can change it through another name only if it is handed what holds that Item (the List[Item] and whatever holds the list), or a reference to a String, which may be that one. A String that some other struct holds is another value. The same goes for the view of an element of a List[String]: the call must be handed the list, or a reference to a String. A String appended to is asked the same way when it is written as a field of a struct: it is another value than the view is into if that struct is not the one that holds the viewed String (a value is the field of one struct only), so pad.input.clipboard.append(pad.texts[i].view()) is accepted. A String that is a field of the very struct, a name, a list's element, or a field that holds a reference says nothing of where it is held, and is still asked:

struct Item { label String; }
struct Dl {
  chars String;
  fn text(self &mut, s &str) { chars.append(s); }
}
struct Ed {
  items List[Item];
  fn grow(self &mut, s &str) { let r = &mut items[0usize]; r.label.append(s); }
}
struct Ui { items List[Item]; }

fn draw(dl &mut Dl, ui &Ui) {
  dl.text(ui.items[0usize].label.view());       // Ok: dl holds a String of its own, and no Item
}

fn twoParams(ed &mut Ed, src &mut Ed) {
  ed.grow(src.items[0usize].label.view());      // Error 1339: ed and src may be one value; the same
                                                //   as let item = src.items[0usize]; item.label.view()
  let c = String.from(src.items[0usize].label.view());
  ed.grow(c.view());                            // Ok: copied out first
}

struct Input { clipboard String; }
struct Pad { texts List[String]; input Input; }

fn copyOut(pad &mut Pad, i usize) {
  pad.input.clipboard.append(pad.texts[i].view());   // Ok: the clipboard is a field of an Input,
}                                                    //   not an element of the list

fn twoEds(a &mut Ed, b &mut Ed) {
  a.items[0usize].label.append(b.items[0usize].label.view());   // Error 1339: both are the label of
}                                                               //   an Item, and a and b may be one value

A collection held as an element of an array is followed as one held by a local or a field is: a borrow into ls[i][j], the element j of the list that is element i of the array ls, is a borrow into that list, whether the array is the owner's own, a struct's field, a multi-dimensional one, or a &mut or &uni slice. Since the compiler cannot tell one index from another, a call or store that could reshape any element of the array is refused while the borrow is still to be used, as one that reshapes the list the borrow is into would be. Reading another element, or swapping two, reshapes nothing and is accepted:

fn grow(x &mut List[i64], e &mut i64) { x.push(1); }

fn two() {
  var ls = [List[i64].empty(), List[i64].empty()];
  ls[0usize].push(7);
  grow(&mut ls[0usize], &mut ls[0usize][0usize]);   // Error 1339: grow pushes, and the second
                                                    //   borrow points into the block that may move
  let e = &mut ls[0usize][0usize];
  ls[1usize].push(1);                               // Error 1339: any element of the array may be
  *e;                                               //   the one e is into
}

Stores#

A store is asked as a call is, for it is no call but could do the same. While such a borrow is still to be used, an assignment or a swap through another name is refused when it replaces the collection whole (*m = List[String].empty();), a struct holding one, or an element that owns something, or a field of one, found by indexing the collection (m[0usize] = s finalizes the old String, so a view of its bytes dangles). So is a store over a field that owns the collection through an owning reference (Uni, Rc, Arc, or a region a program declares), and a store over the whole struct that holds it. These are refused with error 1426:

struct Item { pub n i64; }
struct Ship { pub fuel i64; pub vec Uni[List[Item]]; }

fn whole(l &mut List[String], m &mut List[String]) usize {
  let t = l[0usize].view();
  *m = List[String].empty();    // Error 1426: the old list is freed
  t.len;
}

fn elem(l &mut List[String], m &mut List[String], s String) usize {
  let t = l[0usize].view();
  m[0usize] = s;                // Error 1426: the old String is finalized
  t.len;
}

fn field(a &mut Ship, b &mut Ship) i64 {
  let e = a.vec[0usize];
  b.vec = new Uni[List[Item]](List[Item].empty());   // Error 1426: the list the owner held is freed
  e.n;
}

fn wholeShip(a &mut Ship, b &mut Ship, s Ship) i64 {
  let e = a.vec[0usize];
  *b = s;                       // Error 1426, the same
  e.n;
}

Writing an element that owns nothing in place is not refused, nor is replacing a field of an element through the reference that borrowed the element:

fn setPt(l &mut List[Pt], m &mut List[Pt], p Pt) i64 {
  let e = l[0usize];
  m[0usize] = p;                // Ok: a Pt owns nothing
  e.x;
}

fn fieldOfElem(l &mut List[Pt]) i64 {
  let e = &mut l[0usize];
  e.x = 12;                     // Ok: a field of the element, through the borrow
  e.x;
}

A store over a String field of some other struct, g.log = s for g &mut Log, is refused too while the view of a list's String element is held (error 1426), though a call in the same position is told that a field of another struct is another value (above).

An owner stored over#

The same rule covers what an owning reference (Uni, Rc, Arc, or a region a program declares) owns. Storing over an owner frees what it points at, as replacing a list frees its block, so the owner is the shape-changing value of a borrow into its contents: &mut *s, a field of what it owns, or the owner itself lent where a borrow of its contents is wanted. Another name for the same holder can replace the owner: a second &mut parameter, or a local lent &mut beside a borrow of what it owns (a local may be lent &mut more than once to one call). The borrow is allowed, and refused when something could replace the owner while it is still to be used. A call handed a way to write an owner of that type, whose body (or something it calls) stores over one, is refused with error 1339, and a store over an owner, or over a struct, an array or an Option that holds one by value, with error 1426:

struct Item { pub v i64; }
struct Ship { pub fuel i64; pub it Uni[Item]; }

fn clobber(x &mut Uni[Item], y &mut Item) {
  *x = new Uni[Item](9);         // the old Item is freed
  y.v;                          // and read
}

fn lend() {
  var s = new Uni[Item](1);
  clobber(&mut s, s);           // Error 1339: clobber stores over the owner it is handed,
}                               //   beside a borrow of its contents

fn field(a &mut Ship, b &mut Ship) i64 {
  let y = &mut *a.it;
  b.it = new Uni[Item](9);       // Error 1426: b may be a
  y.v;
}

Writing the value the owner points at, a field of it, or the whole value in place, replaces nothing the owner frees, so a callee that only reads, or writes through the borrow, is accepted, as is an owner of another type, another field of the ship, and an owner stored over after the borrow's last use:

fn writes(x &mut Uni[Item], y &mut Item) {
  x.v = 5;                      // Ok: a field of the Item
  **x = new Item(7);            // Ok: the Item replaced in place, at the same address
  y.v;
}

fn after(a &mut Uni[Item]) i64 {
  let n = (**a).v;
  *a = new Uni[Item](n + 1);     // Ok: the borrow was used up first
  n;
}

An owner of an array (Uni[Array[Item]], what a list's freeze gives) and an owner of a trait object (Uni[Gadget], Rc[mut, Gadget]) are owners like any other, the borrow an element's, or one a method of the trait lent into the object's state. A call through a mutable virtual reference runs code the compiler cannot read, so it is assumed to replace an owner of the type as it is assumed to reshape a collection (ShapeKeeping): calling another method of the object while a borrow its count() lent is held is refused, unless the owner is declared Uni[Gadget + ShapeKeeping].

An enum's variant#

For an enum, nothing is frozen. A reference into the payload of an enum's variant, the binding of case var c Circle on a reference, is a borrow into the enum, through a mut or ro reference from anywhere (a parameter, a field reached through a reference, a reference a variable holds) as well as a borrow of a local made where it is matched. It is allowed, and the fields of the variant may be written through it. It is refused only if something could change which variant the enum holds while it is still to be used: the enum stored over whole or swapped, through the same name or another, or handed to a call whose body (or what it calls) does either, or whose body the compiler cannot read. Writing a field within the variant, by the binding or by the enum's name, or a field the variants share, is not a change:

enum Shape {
  struct Circle { pub radius i32; }
  struct Rect { pub x i32; pub w i32; pub h i32; }
}

fn grow(s &mut Shape) {
  match s {
    case var c Circle { c.radius = 2; }   // Ok: nothing changes the variant while c is used
    else {}
  }
}
fn restart(s &mut Shape) i32 {
  match s {
    case var c Circle {
      c.radius = 2;
      *s = Shape.Rect(1, 2, 3);
      c.radius;                 // Error 1425: 'c', a reference into one of the variants of a 'Shape'
    }                           //   value, is used here after the store, which could change which
    else { 0; }                 //   variant the 'Shape' value holds
  }
}

The same store after the narrowed reference's last use is fine, which is how a state machine moves to its next state. A &mut borrow of a local is an alias loan and is asked the same; a read-only borrow of a local is frozen (What a borrow leaves its source free to do), and a uni, imm or mut1 reference has no other name to change it, so neither is asked. A name narrowed by a test, if s is Circle, is followed the same way (flow typing). A value owned by an Rc is another owner of the value, and no loan stands for the other owners: where none can store another variant (Rc[imm, T]) the binding is a copy of the owner typed as the variant, and where one could (mut, ro) it is refused (Error 1034: A 'mut' reference to Shape, held by an owner (an 'Rc'), may not be narrowed to a reference into one of its variants).

The cures#

A refusal is cured in one of three ways, each of which keeps the borrow:

  • Make the source &imm, which nothing may write (x &imm String).
  • Make the receiver &uni, which no other name reaches (out &uni String). A &uni reference passed to a call that wants a &uni is borrowed from, so a method taking self &uni may be called twice.
  • Copy what is needed out first, so that no borrow is left to wait.
fn appendImm(out &mut String, x &imm String) { out.append(x.view()); }
fn appendUni(out &uni String, x &String) { out.append(x.view()); }

fn appendCopy(out &mut String, x &String) {
  let c = String.from(x.view());
  out.append(c.view());
}

fn twice(s &uni String, x &String) {
  appendUni(s, x);
  appendUni(s, x);              // Ok: the first call borrowed from s, and has given it back
}

Calls through a virtual reference: ShapeKeeping#

Rarely needed, but it decides whether a visitor or a callback can be called while a collection is borrowed. A call through a virtual reference, &<fn(x &mut World) or &<mut Scorer, runs code the compiler cannot read where the call is. So it is assumed to reshape whatever it is handed that reaches the collection, and the borrow ends at the call (error 1339):

struct Entity { pub hp i64; }
struct World { pub score i64; pub entities List[Entity]; }

fn visit(w &mut World, f &<fn(x &mut World)) i64 {
  let e = w.entities[0usize];
  f(w);                         // Error 1339: f's body is not visible, so it may push
  e.hp;
}

A mutable virtual reference, &<mut fn(...) or &<mut Trait, is assumed to reach the collection even when the call is handed nothing that does: the closure behind it may have captured the list (by &mut), the struct behind it may hold it, and the type that says so is hidden. So a borrow into a collection reached through a shared path ends at the call, however the call is spelled:

fn visit[T](xs &List[T], f &<mut fn(x &T)) {
  each x in xs.iter() { f(x); }  // Error 1339: f may be a closure that pushes to xs, which moves
}                                //   the block iter reads

A read-only virtual reference (&<fn, &<Trait with methods that take self &) can write nothing it reaches, and is not asked. A list lent &imm is one nothing can write, so visit(xs &imm List[T], ...) is Ok (its caller needs a list that is imm); and the mark below promises the same of the closure.

The reference can promise otherwise. ShapeKeeping, the twin of ShapeChanging, is a marker the compiler finds, like Shareable, and is written after a + in the type of the reference or the owner (Closure references). A call through such a reference keeps the borrows of the caller:

fn visit(w &mut World, f &<fn(x &mut World) + ShapeKeeping) i64 {
  let e = w.entities[0usize];
  f(w);                         // Ok: whatever f is, it reshapes nothing it was handed
  e.hp;
}

trait Scorer { pub fn bump(self &mut, w &mut World); }

fn tune(w &mut World, s &<mut Scorer + ShapeKeeping) i64 {
  let e = w.entities[0usize];
  s.bump(w);                    // Ok, the same
  e.hp;
}

The promise is held by the closure or the struct put behind the reference, where it becomes one, as a marker like + Shareable is. It holds when nothing in the body of the closure, or of the methods the trait dispatches to, or in what they call, reshapes a collection that it reaches through a path others share: through a parameter that is a &mut (or holds one), or through a variable a closure borrows, or a reference a struct holds. The question is the one a call is asked above, put to the body. Writing a field or an element, the method's own new collections, a parameter held by value or &uni, and reading through & or &imm are all free:

visit(w, fn (x &mut World) { x.score = x.score + 1; x.entities[0usize].hp = 6; });   // Ok

visit(w, fn (x &mut World) { x.entities.push(new Entity(1)); });
    // Error 1500: this closure calls push on a List that it reaches through a &mut parameter

fn count(w &mut World, log &mut List[i64]) i64 {
  visit(w, fn (x &mut World) { log.push(1); });   // Error 1500: the log it borrows is as shared as w
}

The check is as coarse as the one for a call, for the same reasons. A closure that pushes to a list of log's type is refused even if the caller borrowed from some other list of that type, and so is a method that pushes to a list the struct owns when a parameter reaches a list of that type. A function named with &, &grow, is held by its body; a function reference held in a variable has none the compiler can read, and is refused. A type does not declare ShapeKeeping.

A struct of another package can be put behind such a reference too. Its package's include file carries no method bodies, so the package's own compile does the reading and the file says the result: a struct all of whose methods (the public ones that take self) reshape nothing they reach through a shared mutable path is written there with ShapeKeeping after its is. The importer takes the struct as the file says. One method that does reshape, even one the trait never calls, leaves the marker off, and the whole struct is refused. A function of another package named with & has no body to read, and is refused.

A borrow into a var global#

A function may write a global by its name, whatever it is handed, and the check does not keep a summary of which globals a function writes. So a borrow into a var global (the view of a String it holds, an element of a List it holds), or into anything reached from one, is asked of every call made while it is still to be used, or while it is being handed to a call, whether or not the call is handed the global. The call is refused (error 1339) when it, or something it calls, reshapes a collection of that type through a global, as above, or when it is code the compiler cannot read: a call through a virtual reference, a closure or any function value (a &fn held in a parameter, a field, a global or a local, including one a callee makes); an extern C function handed anything whose type can reach something callable (a &fn, a virtual reference, a closure or trait object an owner holds, directly or in a struct or behind a pointer it is handed), which C may call back; or a Cone function whose body is not here. A call that reshapes nothing is let through, and so is an extern C function handed nothing callable, such as a print handed the view, as are the intrinsics, an init, and a call through a virtual reference marked + ShapeKeeping. A global that is let cannot change, so a borrow into it is not asked.

struct Item { label String; }
struct Ed {
  items List[Item];
  fn growFirst(self &mut, s &str) { items[0usize].label.append(s); }
}
struct Holder { pub f &<fn(s &str); }

var gl Ed;
var title String;
var count i64;
fn @initpure init() { gl = new Ed(List[Item].empty()); title = String.from("t"); count = 0; }

fn grow(s &str) { gl.growFirst(s); }
fn show(s &str) usize { s.len; }

fn direct() {
  grow(gl.items[0usize].label.view());  // Error 1339: grow writes gl, and moves the String the
}                                       //   view points into

fn readOnlyCallee() usize {
  let v = gl.items[0usize].label.view();
  let n = show("x");                    // Ok: show reshapes nothing
  n + v.len;
}

fn viaClosure(h &Holder) {
  h.f(gl.items[0usize].label.view());   // Error 1339: h.f is code the compiler cannot read,
}                                       //   and may write gl

A global named from C is not followed: a global of a mod @c module is an external symbol, and an extern function handed nothing could write it.

The cure for a call that may reshape is to read what is needed out of the global before it: a number, or a copy of the String made by a method of the global itself (title.copy()), and to hold that:

fn copied() {
  let text = title.copy();              // Ok: a copy of the String, not a borrow of it
  let n = count;                        // Ok: a number
  grow("!");
  show(text.view());
}

A function handed a reference cannot tell that it is the global: the &mut Ed parameter of a function that is called with &mut gl. So where a var global reaches the type of the collection (a global holding a List[Item] reaches List[Item], Item and String), a borrow into it reached through a reference is asked of the same calls. A program with no var global of that type is not affected:

fn useHolder(h &Holder, s &str) { h.f(s); }   // h.f is a closure that may write gl

fn viaParam(h &Holder, g &mut Ed) {
  useHolder(h, g.items[0usize].label.view());  // Error 1339: g may be gl, and the closure may write it
}

What the check does not follow#

The check does not tell two values of one type apart. A function handed a List[Pt] that reshapes one is taken to reshape the one an element was borrowed from, even when the compiler could show they differ, as for a list freshly allocated in the function. (A list the function owns whole, a plain local, is the exception, as above.)

fn ownedFresh(l &mut List[Pt], p Pt) i64 {
  let e = l[0usize];
  let m = new Uni[List[Pt]](List[Pt].empty());
  bump(&mut *m, p);            // Error 1339: refused, though m cannot be l
  e.x;
}

The same holds of owners (above): a callee that replaces some Uni[Node] is taken to replace the one a borrow is into, though it replaces a different node's. A borrow into the owner a method is called through is its receiver and no other name for the owner, so left.insert(v) is asked only of the other arguments.

Nor does it tell which global a function writes: a borrow of a collection a var global could be is refused across a call that reshapes a collection of that type through any global, or that runs code it cannot read (above), even when the global it writes is another. A virtual call or a closure called anywhere in the callee counts, even one handed nothing.

You can now hold a borrow into a list, a dictionary, a string or an enum, know which calls and stores must wait for it, and reach for one of the three cures when one is refused. That completes the borrowed half of this part. Next, the regions: where values that outlive a single function come from.