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.