# Chapter 11: Borrow and Lifetimes

Chapter 10 introduced the verbs that say what a function does with an argument. This chapter covers the borrow forms underneath them and the **lifetime** labels that let the compiler check that a borrowed value does not outlive what it borrows from. All of it is compile-time only: lifetime labels are erased before code generation and cost nothing at run time (Language Reference §3-2.4.2).

The chapter covers `borrow` and `excl borrow`, lifetime parameters (`[lifetime ~name]`, `borrow[~name]`), inferred struct lifetimes, closures and `noescape fn`, `unowned[~name]`, `volatile borrow` — and, just as important for kernel code, which escapes the 1.25 checker detects and which it does not yet.

## `borrow` and `excl borrow`

`borrow x: T` is a shared, read-only borrow — the same thing as `reads`. `excl borrow x: T` is an exclusive, mutable borrow of a struct or `unique` value — the same thing as `edits` for those types. The verbs are the preferred spelling in new code; the `borrow` forms remain valid in `host.native` and freestanding (§3-2.4.9), and they are the forms that take a lifetime label.

```loup
@mode(freestanding)

@repr(C)
struct SavedRegs {
    var rip: uint64;
    var rsp: uint64;
    var rflags: uint64;
}

func interruptsWereOn(borrow regs: SavedRegs): bool {
    return (regs.rflags & 0x200u64) != 0u64
}

func skipInstruction(excl borrow regs: SavedRegs, length: uint64): void {
    regs.rip = regs.rip + length
}

func handleBreakpoint(): bool {
    var regs = SavedRegs(rip = 0x1000u64, rsp = 0x8000u64, rflags = 0x202u64)
    skipInstruction(regs, 1u64)         # an excl borrow parameter is called plainly
    return interruptsWereOn(regs)
}
```

*Listing 11-1: Shared and exclusive borrows of a struct*

Writing through a plain `borrow` is the same `E_READS_MUTATION` you saw in chapter 10. `excl borrow` is limited to structs and `unique` values, because a plain class reference can be aliased:

```loup,does_not_compile
@mode(freestanding)

class IrqStats {
    pub var count: uint64 = 0;
}

func recordIrq(excl borrow stats: IrqStats): void {
    stats.count = stats.count + 1u64
}
```

```text
error[E_EXCL_BORROW_NOT_STRUCT]: E_EXCL_BORROW_NOT_STRUCT: `excl borrow` parameter 'stats' has type `IrqStats` — only struct types and `unique` types may be exclusively borrowed; use `edits` to exclusively borrow a class instance
 --> src/main.lpfree:7:16
  |
7 | func recordIrq(excl borrow stats: IrqStats): void {
  |                ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |
```

The message names the fix: `func recordIrq(edits stats: IrqStats)`, called as `recordIrq(edits: stats)`. `edits` extends exclusive borrowing to class instances, which is one reason the verbs are the preferred spelling.

## Lifetime parameters

A **lifetime** is a compile-time name for how long a borrowed value is valid. A function or type introduces one in its bracket list as `lifetime ~name`, and a borrow tied to it is written `borrow[~name]`. Labels use `~`, not Rust's `'a`.

```loup
#~ @mode(freestanding)
#~
# One line of a boot command line, borrowed from the text it came from.
struct Line[lifetime ~buf] {
    borrow[~buf] text: string;
    var number: uint32;
}

func firstLine[lifetime ~buf](borrow[~buf] cmdline: string): Line[~buf] {
    return Line(text = cmdline, number = 1u32)
}

func lineNumber(cmdline: string): uint32 {
    let line = firstLine(cmdline)      # Line[~buf] for this cmdline
    return line.number
}
```

*Listing 11-2: A lifetime-parameterized struct and a function that returns it*

The signature of `firstLine` states the contract: the returned `Line` borrows from `cmdline`, so it is valid only as long as `cmdline` is. The caller does not write labels; the compiler matches them up at each call (§3-2.4.2, "reborrowing"). `~static` is the built-in region that lasts for the whole program.

### Inferred struct lifetimes

A struct whose non-trivial fields are *all* `borrow` gets an implicit lifetime, so short-lived views need no labels (§3-2.4.4). The inference covers local use; the struct still may not escape the scope of what it borrows, and returning one from a function needs the explicit form:

```loup,does_not_compile
@mode(freestanding)

# A view of one received frame: every field borrows from the frame.
struct FrameView {
    borrow header: string;
    borrow payload: string;
}

func split(borrow frame: string): FrameView {
    return FrameView(header = frame, payload = frame)
}
```

```text
error[E_INFERRED_LIFETIME_RETURN]: E_INFERRED_LIFETIME_RETURN: function 'split' returns 'FrameView', a struct with an inferred lifetime, without a lifetime annotation on its return type — give the struct a `[lifetime ~name]` parameter and return 'FrameView[~name]' (§3-2.4.4)
 --> src/main.lpfree:9:1
  |
9 | func split(borrow frame: string): FrameView {
  | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |
```

Without a label, a caller could not tell what the returned view borrows from. Name the lifetime and tie the parameter to it:

```loup
#~ @mode(freestanding)
#~
struct FrameView[lifetime ~f] {
    borrow[~f] header: string;
    borrow[~f] payload: string;
}

func split[lifetime ~f](borrow[~f] frame: string): FrameView[~f] {
    return FrameView(header = frame, payload = frame)
}

func headerLength(frame: string): int {
    let view = split(frame)
    return view.header.length()
}
```

*Listing 11-3: Returning a view with an explicit lifetime*

Storing a view into a variable that lives longer than its source is caught too:

```loup,does_not_compile
@mode(freestanding)

struct FrameView {
    borrow header: string;
}

func latestHeader(fresh: bool): uint32 {
    var last = FrameView(header = "none")
    if (fresh) {
        let received = "eth0 frame 1"
        let v = FrameView(header = received)
        last = v
    }
    return 0u32
}
```

```text
error[E_INFERRED_LIFETIME_ESCAPE]: E_INFERRED_LIFETIME_ESCAPE: 'v' has an inferred lifetime (it borrows from 'received') and is stored in 'last', declared in an enclosing block, which outlives it (§3-2.4.4)
  --> src/main.lpfree:12:9
   |
12 |         last = v
   |         ^^^^^^^^
   |
```

`received` ends with the `if` block, so `last` would outlive what it points into. Either use the view inside the block, or declare the source in the outer scope.

## Closures that capture borrows

A closure that captures a `borrow[~name]` parameter may only be handed to a `noescape fn` parameter — a plain `fn` value could be stored and called after the borrow is gone (§3-2.4.2 rule 3):

```loup,does_not_compile
@mode(freestanding)

func checksumLater[lifetime ~pkt](borrow[~pkt] payload: string): uint32 {
    let job: fn(): uint32 = func(): uint32 {
        return payload.length() as uint32
    }
    return job()
}
```

```text
error[E_BORROW_CLOSURE_ESCAPE]: E_BORROW_CLOSURE_ESCAPE: a closure capturing 'payload: borrow[~pkt]' is bound to 'job', a plain `fn` binding — a lifetime-capturing closure may only be passed to a `noescape fn` parameter (§3-2.4.2 rule 3)
 --> src/main.lpfree:4:5
  |
4 |     let job: fn(): uint32 = func(): uint32 {
  |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |
```

Pass the closure straight to a function whose parameter is `noescape fn`:

```loup
#~ @mode(freestanding)
#~
func sumOver(n: int, visit: noescape fn(int): uint32): uint32 {
    var total: uint32 = 0
    var i = 0
    while (i < n) {
        total = total + visit(i)
        i = i + 1
    }
    return total
}

func checksumNow[lifetime ~pkt](borrow[~pkt] payload: string): uint32 {
    return sumOver(1, func(i: int): uint32 {
        return payload.length() as uint32
    })
}
```

*Listing 11-4: A borrow-capturing closure passed to a `noescape fn` parameter*

The `noescape` promise is checked on the callee's side as well: a `noescape fn` parameter cannot be stored in a longer-lived `fn` variable.

```loup,does_not_compile
@mode(freestanding)

var onTick: fn(uint64): void = func(t: uint64): void { }

func everyTick(handler: noescape fn(uint64): void): void {
    onTick = handler
}
```

```text
error[E0001]: cannot assign 'noescape fn(uint64): void' to 'fn(uint64): void'
 --> src/main.lpfree:6:5
  |
6 |     onTick = handler
  |     ^^^^^^^^^^^^^^^^
  |
```

A callback that *is* meant to be stored — a timer hook, an interrupt dispatch entry — takes a plain `fn` parameter, and is usually a named module-level function rather than a capturing closure:

```loup
#~ @mode(freestanding)
#~
var onTick: fn(uint64): void = func(t: uint64): void { }

func setTickHandler(handler: fn(uint64): void): void {
    onTick = handler
}

var ticks: uint64 = 0

func countTick(now: uint64): void {
    ticks = now
}

func initTimer(): void {
    setTickHandler(countTick)
}
```

*Listing 11-5: Storing a plain function as a callback*

## `unowned[~name]` back-references

`unowned[~name]` declares a non-owning field whose referent the compiler proves outlives the holder, using scope nesting (§3-2.4.3). The class must declare the lifetime it names:

```loup
#~ @mode(freestanding)
#~
class TreeNode[lifetime ~owner] {
    pub let value: int;
    pub unowned[~owner] var parent: TreeNode[~owner]?;

    new(value: int, parent: TreeNode[~owner]?) {
        this.value = value
        this.parent = parent
    }
}

safe func build[lifetime ~tree](): int {
    let root = TreeNode[~tree](value = 0, parent = None)
    let child = TreeNode[~tree](value = 1, parent = Some(root))
    return child.value
}
```

*Listing 11-6: A child that points back to its parent*

Assigning a referent declared in a narrower scope — a node created inside an `if` block, stored as the parent of one created outside it — is `E_UNOWNED_LIFETIME_MISMATCH`; leaving `[lifetime ~owner]` off the class is `E_UNOWNED_CLASS_NO_PARAM`. In freestanding, where class objects are staged as chapter 7 describes, keep such trees small and prefer index handles into a fixed table for anything long-lived.

## What the 1.25 checker does not catch yet

The region checks above are a conservative, syntactic subset (§3-2.4.2, "What is statically detected"). Two gaps matter for kernel code:

- **Storing a borrowed parameter into a module variable.** The Language Reference makes returning or storing a `reads`/`borrow` parameter beyond the call `E_BORROW_ESCAPE`. That check is not implemented: `saved = name` inside `func stash[lifetime ~user](borrow[~user] name: string)` compiles. Do not cache pointers from a system call's arguments in kernel state; copy the data you need.
- **Outlives constraints.** There is no `where ~a: ~b` syntax; the parser rejects it. Express "this result lives no longer than that input" by giving both the same label.

Treat lifetime labels as checked documentation of these contracts, and keep the pointers that cross a boundary — a system call, an interrupt, a DMA completion — in values whose lifetime the checker *does* follow: parameters, locals and the structs built from them.

## `volatile borrow` — device registers

For memory-mapped registers, the borrow form takes one more keyword: `volatile borrow` tells the compiler that every read and write through the reference is an observable device access that must not be merged, reordered or removed (§3-3.8). Chapter 15 covers it in depth. A frequent mistake is to write it as an annotation, the way some older material did:

```loup,does_not_compile
@mode(freestanding)

@repr(C)
struct UartRegs {
    var data: uint8;
    var lsr: uint8;
}

func uartWrite(@volatile borrow uart: UartRegs, b: uint8): void {
    uart.data = b
}
```

```text
error[E0001]: expected identifier
 --> src/main.lpfree:9:16
  |
9 | func uartWrite(@volatile borrow uart: UartRegs, b: uint8): void {
  |                ^
  |

error[E0002]: expected `{`
 --> src/main.lpfree:9:16
  |
9 | func uartWrite(@volatile borrow uart: UartRegs, b: uint8): void {
  |                ^
  |

error[E0001]: expected identifier
 --> src/main.lpfree:9:17
  |
9 | func uartWrite(@volatile borrow uart: UartRegs, b: uint8): void {
  |                 ^^^^^^^^
  |

error[E0001]: expected expression
  --> src/main.lpfree:11:1
   |
11 | }
   | ^
   |
```

There is no `@volatile` annotation; annotations cannot appear inside a parameter list at all, which is why the parser stops at the `@`. `volatile` is a keyword in front of `borrow`:

```loup
#~ @mode(freestanding)
#~
@repr(C)
struct UartRegs {
    var data: uint8;
    var lsr: uint8;
}

func uartWrite(volatile borrow uart: UartRegs, b: uint8): void {
    while ((uart.lsr & 0x20u8) == 0u8) { }    # every read of lsr reaches the device
    uart.data = b                            # a volatile store
}
```

*Listing 11-7: A `volatile borrow` parameter*

A `volatile borrow` is still a borrow: the lifetime rules apply to it like to any other.

## Summary

- `borrow` = `reads` and `excl borrow` = `edits` for structs and `unique` values; `excl borrow` on a class is `E_EXCL_BORROW_NOT_STRUCT` — use `edits`.
- `[lifetime ~name]` introduces a lifetime; `borrow[~name] T` ties a borrow to it; labels are checked, then erased.
- All-`borrow` structs get an inferred lifetime for local use; returning one needs an explicit label (`E_INFERRED_LIFETIME_RETURN`), and storing one where it outlives its source is `E_INFERRED_LIFETIME_ESCAPE`.
- A closure capturing a lifetime-labelled borrow may only go to a `noescape fn` parameter (`E_BORROW_CLOSURE_ESCAPE`), and a `noescape fn` cannot be stored; stored callbacks are plain `fn` values.
- `unowned[~name]` back-references are checked by scope nesting (`E_UNOWNED_LIFETIME_MISMATCH`).
- Not yet checked: storing a borrowed parameter in a module variable (`E_BORROW_ESCAPE`); there are no `where ~a: ~b` constraints.
- `volatile borrow` marks device-register parameters; `@volatile` does not exist.

Next: unique ownership.
