# Chapter 16: `borrow`, `excl borrow` and `noescape`

Chapter 15's verbs (`reads`, `edits`, `takes`, `shares`) say what a function does with its arguments. This chapter covers three lower-level keywords that say how an argument is *held*:

- `borrow` — the callee uses the caller's reference for the duration of the call, with no reference-count traffic;
- `excl borrow` — the same, plus a compile-time guarantee that nothing else borrows the value during the call;
- `noescape fn` — a function-typed parameter that the callee only calls and never keeps.

They matter most for library code and hot paths in native binaries, where they let the compiler skip retain/release operations and heap allocations. The checks themselves are compile-time rules, so `loup run` reports exactly the same diagnostics as `loup build --target native`, in `host.dual` as well as `host.native`. The normative text is in the Language Reference §3-2.4.1 and §3-2.4.1.1, with the parameter grammar in §1-7.1.

## `borrow` parameters

A tree walk is the classic place for `borrow`. Every recursive call passes a node down and gets a number back; nothing keeps the node:

```loup
@mode(host.native)

class Node {
    pub let name: string;
    pub var children: list[Node] = [];
    new(name: string) { this.name = name; }
}

func countNodes(borrow node: Node): int {
    var total = 1;
    for (child in node.children) {
        total = total + countNodes(borrow child);
    }
    return total;
}

func main(): void {
    let root = Node("src");
    let lib = Node("lib");
    safe {
        lib.children.push(Node("parse.lphc"));
        lib.children.push(Node("emit.lphc"));
        root.children.push(lib);
        root.children.push(Node("main.lphc"));
    }
    println(countNodes(borrow root));
    println(countNodes(root));
}
```

*Listing 16-1: Counting the nodes of a tree through `borrow` parameters*

```text
5
5
```

`borrow` goes where a verb goes, in front of the parameter name. It tells the compiler that the caller's reference outlives the call, so a native build passes the pointer as it is: no retain on entry, no release on exit. In a recursive walk over thousands of nodes, that is thousands of atomic increments and decrements saved.

At the call site, `borrow` is written as a prefix (`countNodes(borrow child)`). The prefix is optional, as the last line of `main` shows; write it where you want the reader to see that the call only borrows.

A `borrow` parameter is read-only, like a `reads` parameter. The difference is emphasis: chapter 15 noted that a `reads` parameter of class type also avoids ARC traffic, but `reads` describes intent and leaves the lowering to the compiler, whereas `borrow` states the zero-cost passing as part of the signature.

### Mistake: changing a borrowed value

Because a borrow is read-only, a helper that normalizes a list in place does not compile:

```loup,does_not_compile
@mode(host.native)

func normalize(borrow words: list[string]): void {
    for (i in 0..words.length()) {
        words[i] = words[i].toLowerCase();
    }
}

func main(): void {
    var words = ["GET", "Post"];
    normalize(words);
    println(words);
}
```

```text
main.lphc:5:9: error[E_READS_MUTATION]: E_READS_MUTATION: parameter 'words' is a `reads` borrow (a parameter without a verb is read-only, §3-2.4.9) and cannot be mutated (element assigned); declare it `edits` and pass `edits: …` to change the caller's value, or copy it into a local first
```

The message calls the parameter a `reads` borrow, because that is how the checker treats `borrow`. Its advice is right: a function that changes the caller's value takes it with `edits`, and the caller acknowledges it:

```loup
#~ @mode(host.native)
func normalize(edits words: list[string]): void {
    for (i in 0..words.length()) {
        words[i] = words[i].toLowerCase();
    }
}

func main(): void {
    var words = ["GET", "Post"];
    normalize(edits: words);
    println(words);
}
```

*Listing 16-2: Changing the caller's list needs `edits`, not `borrow`*

```text
["get", "post"]
```

> **Known gap.** The Language Reference says a plain `borrow` may not escape the call: storing it in a field or a collection, or returning a borrow of a local, is `E_BORROW_ESCAPE` (§3-2.4.8, §1-7.1). Loup 1.25.1 does not check this, so `cache.push(node)` with a `borrow node` parameter compiles. If a function keeps what it is given, declare the parameter `shares` (chapter 15); if a borrow must outlive the call, give it a lifetime (chapter 18).

## `excl borrow` — an exclusive borrow

`excl borrow` is `borrow` plus an exclusivity rule: while the call runs, no other `borrow` or `excl borrow` of the same binding may be active. It applies to struct parameters and to `unique` values (chapter 17). Because a struct's fields are all values, an exclusive borrow of a struct really is exclusive: nothing else can reach the same data (§3-2.4.1.1).

The call site passes the binding without a prefix. The two-word prefix `excl borrow c` does not parse at a call site.

The checker catches two kinds of conflict. The simplest is one binding passed twice to the same call:

```loup,does_not_compile
@mode(host.native)

struct Cursor {
    text: string;
    var pos: int;
}

func syncPosition(excl borrow dst: Cursor, borrow src: Cursor): void {
    dst.pos = src.pos;
}

func main(): void {
    var c = Cursor(text = "GET /", pos = 0);
    syncPosition(c, c);
}
```

```text
main.lphc:14:21: error[E_EXCL_BORROW_ALIAS]: E_EXCL_BORROW_ALIAS: 'c' is passed to `excl borrow` parameter 'dst' while the same binding is simultaneously borrowed by parameter 'src' of this call — an exclusive borrow admits no other active borrow
```

The other is a borrow taken by an earlier statement that is still held. Here `peek` borrows the cursor, and its result is bound to `ch` before `skip` asks for exclusive access:

```loup,does_not_compile
@mode(host.native)

struct Cursor {
    text: string;
    var pos: int;
}

func peek(borrow c: Cursor): string {
    return c.text[c.pos];
}

func skip(excl borrow c: Cursor): void {
    c.pos = c.pos + 1;
}

func main(): void {
    var c = Cursor(text = "GET /", pos = 0);
    let ch = peek(c);
    skip(c);
    println(ch);
}
```

```text
main.lphc:19:10: error[E_EXCL_BORROW_ALIAS]: E_EXCL_BORROW_ALIAS: 'c' is passed to `excl borrow` parameter 'c' while an earlier shared borrow of it is still held by 'ch' — an exclusive borrow admits no other active borrow. A borrow ends when its result is discarded; here it is still bound.
```

The rule is conservative: `ch` holds a copied string, but the checker treats the result of a `borrow` call that is still bound as still borrowing. End the borrow before taking the exclusive one, either by not binding the result or by binding it in an inner block:

```loup
#~ @mode(host.native)
#~
#~ struct Cursor {
#~     text: string;
#~     var pos: int;
#~ }
#~
#~ func peek(borrow c: Cursor): string {
#~     return c.text[c.pos];
#~ }
#~
#~ func skip(excl borrow c: Cursor): void {
#~     c.pos = c.pos + 1;
#~ }
#~
func main(): void {
    var c = Cursor(text = "GET /", pos = 0);
    if (c.pos == 0) {
        let ch = peek(c);
        println(i"first character: {$ ch $}");
    }
    skip(c);
}
```

*Listing 16-3: The shared borrow ends with its block, so the exclusive one is allowed*

```text
first character: G
```

### Known gap: changes made through `excl borrow` stay local

The specification describes `excl borrow` as in-place mutation of the caller's struct. Under `loup run` in 1.25.1 the callee changes a copy instead, and the caller never sees the change:

```loup,not_desired_behavior
#~ @mode(host.native)
#~
struct Cursor {
    text: string;
    var pos: int;
}

func skip(excl borrow c: Cursor): void {
    c.pos = c.pos + 1;
}

func main(): void {
    var c = Cursor(text = "GET /", pos = 0);
    skip(c);
    skip(c);
    println(c.pos);
}
```

```text
0
```

The program should print `2`. Until this is fixed, use `edits` for a function that must change the caller's struct, as Listing 15-3 does. `edits` is specified to be exclusive as well, although its alias check (`E_EDITS_ALIAS`) is not implemented yet (chapter 15).

### Mistake: `excl borrow` on a class

`excl borrow` is refused on a class, a primitive, an enum or `Any`, because an ordinary class instance may have other references that the compiler cannot see:

```loup,does_not_compile
@mode(host.native)

class Account {
    pub var balance: int = 0;
}

func deposit(excl borrow a: Account, amount: int): void {
    println(a.balance + amount);
}

func main(): void {
    deposit(Account(), 10);
}
```

```text
main.lphc:7:14: error[E_EXCL_BORROW_NOT_STRUCT]: E_EXCL_BORROW_NOT_STRUCT: `excl borrow` parameter 'a' has type `Account` — only struct types and `unique` types may be exclusively borrowed; use `edits` to exclusively borrow a class instance
```

The message names the fix: an ordinary class instance is changed through `edits` (with `safe func`, as in Listing 15-4). A `unique class` instance (chapter 17) has exactly one owner, so `excl borrow` is accepted there, and its changes are visible to the caller because the instance is a reference:

```loup
#~ @mode(host.native)
unique class Arena {
    pub var used: int = 0;
}

func reserve(excl borrow a: Arena, bytes: int): void {
    a.used = a.used + bytes;
}

func main(): void {
    let arena = Arena();
    reserve(arena, 64);
    reserve(arena, 16);
    println(arena.used);
}
```

*Listing 16-4: `excl borrow` of a `unique class` instance*

```text
80
```

Two more restrictions apply to `excl borrow`, and both are checked: it may not appear in an `async func` signature (`E_EXCL_BORROW_ASYNC`), and a closure passed to `Thread.spawn`, `Thread.spawnWith` or `ThreadPool.run` may not capture it (`E_EXCL_BORROW_THREAD_CAPTURE`, chapter 20).

## `noescape fn` — closures that stay in the call

A function value normally has to live on the heap: the callee might store it, return it, or hand it to a thread. A parameter declared `noescape fn(...)` promises that the callee only calls it during its own execution. The native compiler can then pass the closure without retaining it and, when its captures allow, keep it entirely on the caller's stack (§3-2.4.1).

Here is a retry helper. It calls `tryOnce` up to `limit` times and never keeps it:

```loup
@mode(host.native)

func withRetries(limit: int, tryOnce: noescape fn(int): bool): int {
    for (n in 1..limit + 1) {
        if (tryOnce(n)) {
            return n;
        }
    }
    return -1;
}

func main(): void {
    var log: list[string] = [];
    let tries = withRetries(5, func(n: int): bool {
        log.push(i"attempt {$ n $}");
        return n == 3;
    });
    println(log);
    println(i"succeeded after {$ tries $} attempts");
}
```

*Listing 16-5: A retry helper that takes a `noescape fn`*

```text
["attempt 1", "attempt 2", "attempt 3"]
succeeded after 3 attempts
```

The caller writes an ordinary lambda; nothing changes at the call site.

### Mistake: a lambda without a return type

If you leave out the lambda's return type, the compiler infers `Any`, which does not match the parameter's type:

```loup,does_not_compile
@mode(host.native)

func each(items: list[int], body: noescape fn(int): void): void {
    for (x in items) {
        body(x);
    }
}

func main(): void {
    var total = 0;
    each([1, 2, 3], func(x: int) {
        nonlocal total;
        total = total + x;
    });
    println(total);
}
```

```text
main.lphc:11:21: error[E0001]: argument 2 of 'each': expected 'noescape fn(int): void', found 'fn(int): Any'
```

Write `func(x: int): void { … }`. The same applies to any function-typed parameter, `noescape` or not.

### Mistake: keeping a `noescape` function

The compiler checks the promise. A button that stores its click handler is the natural way to break it:

```loup,does_not_compile
@mode(host.native)

class Button {
    pub var onClick: fn(): void = func(): void {};
}

safe func setHandler(edits b: Button, handler: noescape fn(): void): void {
    b.onClick = handler;
}

func main(): void {
    let ok = Button();
    setHandler(edits: ok, func(): void { println("saved"); });
    ok.onClick();
}
```

```text
main.lphc:8:5: error[E_NOESCAPE_ESCAPE]: E_NOESCAPE_ESCAPE: a `noescape fn` value may be called only within the call frame it was passed into, so it cannot be stored in a field
```

`setHandler` really does keep the function, so `noescape` is the wrong promise. Declare the parameter as a plain `fn(): void`; the closure then lives on the heap and is reference-counted like any other value:

```loup
#~ @mode(host.native)
#~
#~ class Button {
#~     pub var onClick: fn(): void = func(): void {};
#~ }
#~
safe func setHandler(edits b: Button, handler: fn(): void): void {
    b.onClick = handler;
}
#~
#~ func main(): void {
#~     let ok = Button();
#~     setHandler(edits: ok, func(): void { println("saved"); });
#~     ok.onClick();
#~ }
```

*Listing 16-6: A handler that is stored takes a plain `fn`*

```text
saved
```

`E_NOESCAPE_ESCAPE` also reports returning the value ("cannot be returned from a function"), capturing it in a closure bound to a plain `fn` variable, and handing it to `Thread.spawn` ("whose thread outlives the frame"). Passing it to a parameter of plain `fn` type is a type error (`expected 'fn(): void', found 'noescape fn(): void'`).

> **Known gap.** Pushing a `noescape` value into a collection (`b.handlers.push(handler)`) is not reported by 1.25.1, although it is an escape. Treat it as one.

## Putting them together

A library function that iterates with a callback can combine both keywords: the list is borrowed, the callback does not escape, and each element is borrowed into the callback. Verbs and `borrow` may appear in function types too (§1-7.1):

```loup
#~ @mode(host.native)
func forEach[T](borrow items: list[T], body: noescape fn(borrow T): void): void {
    for (item in items) {
        body(borrow item);
    }
}

func main(): void {
    var longest = "";
    forEach(["pear", "banana", "fig"], func(borrow word: string): void {
        nonlocal longest;
        if (word.length() > longest.length()) {
            longest = word;
        }
    });
    println(longest);
}
```

*Listing 16-7: A borrowed list and a non-escaping callback*

```text
banana
```

As specified, a native build of this loop retains nothing: not the list, not the closure, not the elements.

## Choosing between verbs and these keywords

- **Application code:** use the verbs. `reads` already avoids ARC traffic for class parameters, and `edits` is how a function changes its caller's value.
- **Library code on hot paths:** write `borrow` where you want zero-cost passing to be part of the contract, and `noescape fn` for callbacks that are only called.
- **Exclusive access to a struct:** `excl borrow` documents and checks exclusivity, but use `edits` when the caller must see the changes (see the known gap above).
- **Anything that keeps what it is given:** `shares` for values, a plain `fn` type for functions.

## Summary

- `borrow` passes the caller's reference without retain/release; the parameter is read-only (`E_READS_MUTATION`), and the call-site `borrow` prefix is optional.
- `excl borrow` adds exclusivity for struct and `unique` parameters: a second borrow in the same call, or a still-bound earlier borrow, is `E_EXCL_BORROW_ALIAS`; a class parameter is `E_EXCL_BORROW_NOT_STRUCT`. Under `loup run` changes to an `excl borrow` struct do not reach the caller yet, so use `edits` for that.
- `noescape fn` parameters may only be called; storing, returning, capturing or spawning them is `E_NOESCAPE_ESCAPE`. Lambdas passed to them need an explicit return type.
- `E_BORROW_ESCAPE` for plain borrows is specified but not checked in 1.25.1.
- Chapter 17 introduces `unique`, single ownership without reference counting.
