Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Returned references

A function or method returning &T or &mut T borrows its result from its first reference parameter, the origin. A slice view parameter, &[T] or &mut [T], counts as a reference parameter. A function without a reference parameter cannot return a reference, and a mutable result requires an &mut origin. Every return path must originate in the origin; later reference parameters, local owners, and owned parameters cannot escape. Direct parameter references, reborrows, field and element projections, borrowed-match payloads, and forwarding calls are supported. Branches may use explicit returns; conditional reference expressions are unsupported.

class Pair:
    first: i64
    second: i64

def first(pair: &Pair) -> &i64:
    &pair.first

def main() -> Result[(), IoError]:
    pair = Pair(3, 4)
    value = first(&pair)
    print(*value)?
    Ok(())
3

Later reference parameters are ordinary inputs. Their loans end when the call returns, so a temporary key or a borrow of another owner does not restrict the result. For a method, the origin is its reference receiver:

class Scores:
    names: list[str]
    points: list[i64]

    def points_of(self: &mut Scores, name: &str) -> &mut i64:
        for i, candidate in enumerate(&self.names):
            if candidate == name:
                return &mut self.points[i]
        &mut self.points[-1]

def main() -> Result[(), AllocError | IoError | OverflowError[i64]]:
    mut scores = Scores(["ada", "bob"]?, [3, 5]?)
    mut name = "bob"
    points = scores.points_of(&name)
    name = "eve"
    *points = *points + 10
    print(scores.points)?
    print(name)?
    Ok(())
[3, 15]
eve

This small lookup assumes equally sized, nonempty lists and uses the last score as its fallback. Enumeration avoids manual index arithmetic while keeping the reference-returning method infallible. A general lookup can return an optional reference instead.

To return a borrow of a different argument, make it the first reference parameter. A function cannot choose between two arguments; that relationship needs annotations Plenty does not have. Signatures alone fix the origin, so Callable types and generic functions follow the same rule.

The result extends its origin loan until its last use. A body that directly returns its parameter or a fixed field projection permits a precise returned field loan; the original call still requires access to its whole argument. Indexed origins, control-dependent returns, and other bodies conservatively protect the whole argument, even after further projection.

A &mut T result can be assigned through directly, without a binding: *f(&mut xs) = value and *f(&mut xs) += value call f once and write its referent. The call runs first, as in r = f(&mut xs); *r = value, so its loan is live through the right-hand side: *f(&mut xs) = xs[1] is a conflicting borrow. Read the value into a local first.

def head(xs: &mut [i64]) -> &mut i64:
    &mut xs[0]

def main() -> Result[(), AllocError | IoError | OverflowError[i64]]:
    mut xs = [1, 2, 3]?
    *head(&mut xs[:]) = 9
    *head(&mut xs[1:]) += 10
    print(xs)?
    Ok(())
[9, 12, 3]

A caller may bind a result from named storage and reborrow it. A temporary origin survives only its full expression, the statement or condition that evaluates it. A shared temporary passed to an ordinary function follows this rule: len(view("text")) can use a returned &str, but r = view("text") is rejected even when r is unused. A temporary method receiver, such as make().label(), follows the same rule: the result may be used within the statement, and the receiver drops at its end. Binding the result, or returning it, is rejected:

class Point:
    x: i64
    name: str

    def label(self) -> &str:
        &self.name

def make(name: str) -> Point:
    Point(0, name)

def main() -> Result[(), IoError]:
    print(make("origin").label())?
    print(len(make("east").label()))?
    Ok(())
origin
4

A temporary receiver that itself borrows other storage, such as a returned &Point in pick(&a).view(), cannot lend a returned reference: its loan of that storage would end before the result is used. Bind the receiver first, p = pick(&a), then call p.view().

A method on an element or field of a temporary, as in [make("a")]?[0].label() or holder.items()[0].view(), borrows that element in place. An owned temporary lives to the end of the statement, as a temporary receiver does; a returned reference lends as a named reference does.

Returned references cannot enter aggregates, scoped workers with temporary origins, or generator suspension. No lifetime annotation or callee-body inference changes the signature’s first-reference origin.

A borrow that may be absent or may fail is returned as Option[&T] or Result[&T, E] under the same origin rule; see optional and fallible references.