Lifetime annotations: how long a borrow lives
On this page
- Lifetime annotations on a function
- A returned borrow
- A mutable borrow that could store another
- Inside the function
- The global lifetime: 'static
- Implementing a trait
- Lifetime annotations on a struct
- Declaring a struct's lifetimes
- A struct's lifetimes in a signature
- Lifetimes in a struct's methods
- Two lifetimes in one struct
- Ordering lifetimes: where clauses
- References to references
- Implied bounds
- Lifetime bounds on generics and virtual references
Inside one function the compiler sees where every borrow came from, and so how long it may live. A call hides that: the function called cannot see where its borrowed parameters came from, and the caller cannot see what the function does with them. A function's signature bridges the gap, and where one borrow could be taken for another, it names their lifetimes. This page covers when that is needed and how to write it, on functions, structs and generics. Afterwards you can write functions that return and store borrows, and read the compiler's lifetime errors:
mod budget;
import stdio use *;
// The result is borrowed from limit only. Naming scratch's lifetime 'a,
// apart from the rest, tells every caller so.
fn cap(limit &i32, scratch &'a i32) &i32 {
if *scratch > *limit {
print <- "over the limit by ", *scratch - *limit, "\n";
}
limit;
}
fn main() {
var allowed &i32;
let limit = 50;
{
let spent = 65; // lives only in this block
allowed = cap(&limit, &spent);
}
print <- "allowed: ", *allowed, "\n";
}
It prints:
over the limit by 15 allowed: 50
Try this: delete the 'a. The compiler now takes the result to be borrowed from either argument, and refuses the program: 'spent', declared here, goes out of scope while 'allowed' still holds a borrow of it.
Lifetime annotations on a function#
A lifetime is a name written right after a reference's &, before its permission: &'a i32, &'a mut i32, &'a Array[u8] (the token). Two references with the same name have the same lifetime, as far as the function is concerned; names apart are unrelated. Here the first result has the lifetime of a and the second that of b:
fn dostuff(a &'a i32, b &'b i32) &'a i32, &'b i32 { a, b; }
fn first(a &'a i32, b &'b mut i32) &'a i32 { a; }
Borrowed references written with no name all share one unnamed lifetime, as though they were written with the same name, so this dostuff is the same as the one above:
fn dostuff(a &i32, b &'b i32) &i32, &'b i32 { a, b; }
Two references called "the same lifetime" may in fact have different ones in the caller. What they share is the shorter, since that is the only lifetime both are sure to have, and the caller is held to it. That is always safe, and sometimes stricter than it needs to be, which is when a name helps.
No name is needed in a function that receives fewer than two borrowed references, nor in one whose borrowed parameters are all read-only (ro) and that returns no borrow. Beyond that simple test, two situations may need names: a returned borrow, and a mutable borrow that could store another.
A returned borrow#
When a function returns a borrow and takes two or more, its caller must know which the result could be. The opening example without its 'a is the case:
fn refswitch(nbr i32, ref1 &i32, ref2 &i32) &i32 {
if nbr < 0 { ref1; } else { ref2; }
}
fn caller(nbr i32) i32 {
var ref &i32;
let outer = 5;
{
let inner = 10; // Error 1168: 'inner', declared here, goes out of scope while 'ref'
ref = refswitch(nbr, &outer, &inner); // still holds a borrow of it
}
*ref;
}
caller cannot know whether refswitch returns the borrow of inner or of outer, and the borrow of inner would be read after inner is gone. If instead the signature says the result comes from ref1 only, by giving ref2 a lifetime of its own, the caller is safe and compiles:
fn refswitch(nbr i32, ref1 &i32, ref2 &'a i32) &i32 {
ref1;
}
Had the signature said the result comes from ref2, fn refswitch(nbr i32, ref1 &'a i32, ref2 &i32) &i32 { ref2; }, the caller would be refused again. The lesson: give a returned borrow the lifetime of the borrows it could come from, and every borrow it cannot come from a different one. Marked so, the rules hold on both sides of the call.
The caller reads the signature this way. A returned borrow has the lifetime of the shortest borrow passed for a parameter whose lifetime it may hold, and keeps those arguments frozen while it is used; a method's receiver counts as one of the arguments. Only the argument the result may hold is frozen:
fn pick(x &'a i32, y &'b i32) &'a i32 { x; }
var a = 1;
var b = 2;
let r = pick(&a, &b);
b = 6; // fine: r holds nothing of b
a = 5; // Error 1168: 'a' is borrowed, and that borrow is used again below.
print <- *r; // It may not be changed until after that last use.
struct S {
pub n i32;
pub fn get(self &) &i32 { &n; }
}
var s = new S(1);
let r = s.get();
s.n = 3; // Error 1168: 's' is borrowed (by 'get'), and that borrow is used again below.
print <- *r;
Where the result may hold only a lifetime of what an argument points at, not the argument itself (a struct's lifetime held by the receiver of one of its methods, or the inner lifetime of a &'a mut &'b T), the call keeps frozen only what the argument holds in that lifetime, not the argument, nor a local the argument borrows (reference layers). The full rules for a returned borrow are with borrowed references.
A mutable borrow that could store another#
When a call is handed two or more borrows, one of them mutable, the function could store one borrow inside another:
fn refstore(refmut &mut &i32, refval &i32) {
*refmut = refval; // allowed: both have the one unnamed lifetime
}
fn tryit() i32 {
let i = 5;
var a = &i;
{
let b = 10; // Error 1168: 'b', declared here, goes out of scope while 'a' still
refstore(&mut a, &b); // holds a borrow of it
}
*a; // used again here
}
refstore treats both borrows as having the same, shortest lifetime, and tryit knows that is b's block and that refstore might store the borrow of b in a, as it does. That alone is no error: a variable's lifetime is that of what it holds now, and had a not been used again, the call would be fine. What is refused is reading a after b's block, where it might point at b, gone, and the error is reported where b goes out of scope. Had a been what a parameter points at, or a global, which outlive tryit, the call itself would be refused, since nothing after it could keep the borrow of b from outliving b.
The same risk arises whenever a call is handed a mutable (or unique) borrow of anything that can hold a borrow, not just of a borrowed reference: a struct with a borrowed-reference field, an Option or List of borrows, or a slice of them. A method's self &mut counts the same way, so after a setter that stores its argument in a field of its receiver, or a push onto a list of borrows, the receiver may hold the borrow it was handed, and may not be used after that borrow's source is gone:
struct Holder {
pub r &i32;
pub fn set(self &mut, r &i32) { self.r = r; }
}
fn tryset() i32 {
let i = 5;
var h = new Holder(&i);
{
let b = 10; // Error 1168: 'b', declared here, goes out of scope while 'h' still
h.set(&b); // holds a borrow of it
}
*h.r;
}
Handing over a borrow that lives at least as long as the place it could be stored in is always allowed.
There are two cures. One is to declare b in the same scope as a, so their lifetimes really are the same. The other is for the function to promise it will not store one in the other, by giving them different lifetimes. Then it may read both, and the caller above compiles:
// 'b says refval is unrelated to what refmut holds, so it cannot be stored there
fn larger(refmut &mut &i32, refval &'b i32) i32 {
if **refmut > *refval { **refmut; } else { *refval; }
}
The lesson: when you pass a mutable borrow, give every other borrow a different lifetime unless the function means to store it there.
What a method may never store in its receiver is a borrow of the receiver's own storage. With buf and held both fields of self, pub fn grab(self &mut) { held = buf.view(); } would leave a value holding a borrow of one of its own fields, which its owner may then change or move (refill buf, push past its capacity) while held is still read. It is refused where it is written (Error 1409: Stored into 'self.held', the value carries a borrow of 'self.buf' ... a value may not keep a borrow of its own storage after the call). The same holds for a function handed a &mut to the value, and for a call that could store the borrow (words.push(text.view()), both fields of self). Store an index or an offset instead, or hold the buffer somewhere else and borrow it from there. A borrow the method was given may be stored, as above.
Inside the function#
The names bind the function called as well. Inside it, a borrow the caller lent may be returned only as a lifetime it is written with, or one its where clause or its types order shorter, and stored only where what a parameter points at holds such a lifetime. No order between two names is inferred, beyond what a where clause declares and what a borrow of a value holding lifetimes implies (implied bounds):
struct R { pub n i64; }
fn bad(x &'a R, y &'b R) &'a R {
y; // Error 1064: Returned value carries the borrow the caller lent through 'y',
} // whose lifetime the result's type does not name
fn ok(x &'a R, y &'b R) &'a R where 'b >= 'a {
y; // Ok: 'b lasts at least as long as 'a
}
fn put(d &mut &'b R, s &'a R) {
*d = s; // Error 1064: Stored where 'd' points, the value carries the borrow the caller
} // lent through 's', whose lifetime is not one held there
The global lifetime: 'static#
The lifetime of the whole program is 'static. A 'static parameter takes only a global borrow, a borrow of a global variable, and a borrow the caller lent may not be returned as 'static:
let GLOBAL R = new R(1);
fn keep(s &'static R) i64 { s.n; }
fn leak(x &R) &'static R {
x; // Error 1064: Returned value carries the borrow the caller lent through
} // 'x', whose lifetime the result's type does not name
fn caller() i64 {
let v = new R(5);
keep(&GLOBAL); // Ok
keep(&v); // Error 1085: This parameter's lifetime is 'static, so the borrow handed
} // to it must be global
'static may be written on a parameter's own reference or in the result, but not inside a parameter's type: fn read(slot &mut &'static R) is refused (Error 1232), since what a caller passes there is not checked to be global.
Implementing a trait#
A trait's implementation may promise more than the trait does: a result of 'static or of a longer lifetime, and a parameter of any lifetime where the trait's is 'static. A call through the trait is checked against the trait's signature, which holds its result to everything the signature lets it carry, so an implementation that carries less is safe:
struct R { pub n i64; }
let THREE R = new R(3);
trait Source {
pub fn top(self &) &R;
pub fn note(self &, x &'static R);
}
struct Cursor['a] is Source {
pub items &'a Array[R];
pub fn top(self &) &'static R { &THREE; } // promises more: a global borrow
pub fn note(self &, x &R) { print <- x.n; } // takes more: a borrow of any lifetime
}
The reverse is refused: an implementation that takes only a 'static borrow where the trait's parameter takes any, or whose result is tied to an argument where the trait's result is tied to the receiver, does not meet the trait (Error 1013: Type declares note, but none of what it declares has the signature Source requires).
Lifetime annotations on a struct#
A struct that holds borrows is as limited as the borrows it holds, and its type must say so:
struct Wrapper['a, 'b] {
pub refa &'a i32;
pub refb &'b i32;
}
Each Wrapper holds two borrows, each with a lifetime of its own, and a Wrapper value lives no longer than the shorter of them:
fn makewrap(valref &i32) Wrapper {
let val = 10;
new Wrapper(valref, &val); // Error 1064: Returned value carries a borrow of 'val', which it
} // would outlive: 'val' belongs to this function.
The Wrapper is made in makewrap from a borrow of one of its locals, so it is limited to that function and may not be returned.
Declaring a struct's lifetimes#
A struct (or an enum) declares the lifetimes its fields name in its square brackets, apart from its type parameters: struct Wrapper['a, 'b], struct Cursor['a, T]. Its fields name those lifetimes and no others. A struct whose fields name exactly one lifetime may leave it out of its brackets, and that one is taken as declared; one whose fields name two or more declares them all, so that a misspelled name is caught rather than taken for one more lifetime (Error 1233: Two's fields name more than one lifetime, so its brackets declare each). Every borrow a struct declaring lifetimes holds names one of them: an unnamed borrow there would be of whatever lifetime each signature using the struct leaves unnamed.
A lifetime changes neither a struct's layout nor its code, so it is never instanced: Wrapper['a, 'b] and Wrapper are one type, and a generic struct has one instance per type argument, none per lifetime. A struct is constrained by a lifetime only by holding a borrow: one declaring lifetimes but holding no borrow travels anywhere, and a generic one is constrained per instance (List[&R] is, List[i32] is not).
A struct's lifetimes in a signature#
The rules for a function's borrows apply to values that hold borrows too. A struct's use names its lifetimes in its brackets, one for each it declares, in order:
fn makewrap(valref &i32) &i32 {
let val = 10;
getref(new Wrapper(valref, &val));
}
fn getref(wrap Wrapper['a, 'b]) &'a i32 {
wrap.refa;
}
getref returns the borrow held in refa, and its result's lifetime says so, so makewrap may return what it gives: the borrow of valref, not of the local val. Were getref to return refb, as &'b i32, makewrap would be refused for returning a borrow of its own local. A use naming no lifetimes, plain Wrapper, gives every lifetime the struct declares the signature's unnamed one.
A value passed by value that holds a borrow counts as one of the lifetimes its type names, the unnamed one where it names none, both for the lifetime a returned borrow takes and for where a borrow may be stored. Here the result is of h's 'a, so it outlives the borrow of y:
struct Holder['a] { pub r &'a i32; }
fn pick(h Holder['a], y &'b i32) &'a i32 { h.r; }
fn main() i32 {
let x = 1;
var r = &x;
{
let y = 2;
r = pick(new Holder(&x), &y); // Ok: the result is of h's 'a, not y's
}
*r;
}
Written without lifetimes, fn pick(h Holder, y &i32) &i32, the Holder takes the unnamed lifetime that y has, the result may hold either, and the same call is refused where r is used after y is gone.
Lifetimes in a struct's methods#
Inside a struct's methods, Self (and so an unwritten self type) is the struct with its own lifetimes, which act there as the caller's lifetimes do. A method may so promise that its result points where a field points, not into the struct. This cursor hands out the items of an array it borrows:
struct Cursor['a] {
pub items &'a Array[R];
pub at usize;
pub fn next(self &mut) Option[&'a R] {
if at < items.len() {
let r = &items[at];
at = at + 1usize;
return Some[&R](r);
}
None[&R];
}
}
fn firstOf(items &Array[R]) Option[&R] {
var c = new Cursor(items, 0usize);
c.next(); // Ok: points where items points, not into c
}
Because next's result is of the cursor's 'a, not of the &mut borrow of self, a call keeps frozen only what the cursor holds in 'a, and the result may outlive the cursor. Written without lifetimes, the result would share the receiver's borrow, and could not be returned. The method in turn may return only what its fields of 'a point at: &self.at, a borrow of the cursor itself, is of self's own lifetime, not 'a.
Two lifetimes in one struct#
A struct may hold borrows of two unrelated lifetimes. A value of it is usable wherever all its lifetimes hold, and what one of them holds is kept apart from the other's. This parser borrows its text from the caller and a scratch area from wherever it was made:
struct Scratch { pub words i32; }
struct Parser['src, 'ar] {
pub text &'src Array[u8];
pub ar &'ar mut Scratch;
pub at usize;
pub fn word(self &mut) &'src Array[u8] {
ar.words += 1;
let start = at;
while at < text.len() and text[at] != 32u8 { at += 1; }
&text[start ..< at];
}
}
fn firstWord(text &Array[u8]) &Array[u8] {
var scratch = new Scratch(0);
var p = new Parser(text, &mut scratch, 0usize);
p.word(); // Ok: the word is of the text, not of the local scratch
}
With one lifetime for both fields, the word could hold anything the parser holds, the local scratch included, and could not be returned (Error 1064: Returned value carries a borrow of 'scratch'). A borrow of one of two unrelated lifetimes may not be stored where the other's are held: that needs a where clause ordering them (below). A call whose signature names the struct's lifetimes as one, or orders them beyond the struct's own order, may move a borrow from one field to another, so after it the caller no longer knows which field holds which borrow, and keeps them all as one.
Ordering lifetimes: where clauses#
Two lifetimes a signature names are unrelated unless its where clause orders them. 'a >= 'b says 'a lasts at least as long as 'b: a borrow of 'a may then be stored where 'b borrows are held, and returned where a 'b one is promised, but not the reverse. 'a == 'b orders each at least as long as the other. The order is transitive, and nothing beyond what the clauses say, and their transitive closure, is inferred:
fn put(d &mut &'b R, s &'a R) where 'a >= 'b {
*d = s; // Ok: 'a lasts at least as long as 'b
}
fn pick[T](x &'a T, y &'b T) &'b T where 'a >= 'b and T is Copy {
x;
}
A caller reads the same order: it keeps put's second argument alive as long as the place its first points at is used. A where clause orders lifetimes on any function, generic or not, among the lifetimes its signature names; on a struct, struct Pair['a, 'b] where 'a >= 'b, among those it declares, in its methods and in every signature using it; and on a generic type's methods. Lifetime comparisons join each other and type clauses by and only: a lifetime is never instanced, so no instance settles which alternative of an or holds, and the body could rely on none of them. An or or a not over a lifetime comparison is refused (Error 1235: A lifetime comparison is joined to the rest of a 'where' clause by 'and' only).
References to references#
Each layer of a reference to a reference has a lifetime of its own, and what is read through a layer has the lifetime written inside it, not the layer's own:
fn first(x &'a mut &'b R) &'b R {
*x; // Ok: what x points at holds a 'b borrow
}
fn caller(p &R) &R {
var pp = p;
let z = &mut pp;
first(z); // Ok: of what pp holds, p's borrow, not of pp
}
Because the result is of the inner lifetime, the call keeps frozen only what the argument holds in it (p's borrow), not the argument z, nor pp, the local it borrows. After let r = first(z);, pp may still be assigned while r is in use.
A borrow taken through an inner uni reference is a reborrow of it, which lasts no longer than the outer borrow, since the inner reference may be used again once the outer borrow ends: &uni **x for x &'a uni &'b uni R is of 'a, and may not be returned as 'b. Through an inner mut or read-only reference, which a copy may share, it is of the inner lifetime.
Implied bounds#
A borrow of a value holding lifetimes cannot outlast them: the value lives no longer than what it borrows, and the borrow no longer than the value. So wherever a signature names such a borrow, each lifetime the borrowed value holds is understood to last at least as long as the borrow's own, as if a where clause said so. &'a Pair['b] and &'a &'b R each imply 'b >= 'a, and a method's self & in a struct declaring 'a implies that 'a outlasts the unannotated borrow of self. So a method written with no lifetime of its own may return what a field points at, the result taking the shorter lifetime of the receiver's borrow:
struct H['a] {
pub r &'a R;
pub fn get(self &) &R { r; } // Ok: 'a outlasts the borrow of self
}
fn inner(x &'a &'b R) &'a R {
*x; // Ok: 'b outlasts 'a
}
An implied bound runs one way only: the borrow of a Pair['b], of 'a, may not be returned as 'b. Two lifetimes no borrow relates and no where clause orders stay unrelated. A caller reads the same order, as it reads a where clause.
Lifetime bounds on generics and virtual references#
A lifetime is named on a borrowed reference or on a struct's use. A type whose insides a signature cannot see, a generic function's type parameter or what a virtual reference points at, is given a bound instead, + 'a: any borrow inside lives at least as long as 'a, a lifetime the signature names. A bound adds what it says to the signature's order, as a where clause does; nothing is solved for.
On a type parameter. [T + 'a], [T Trait + 'a], or in the where clause, where T + 'a or where T is Trait + 'a (Generics). Inside the function, a value of type T may be stored where 'a borrows are held, or returned as 'a; without the bound, the borrows a T holds are of a lifetime of their own, which is neither. A generic function is checked as each call instances it:
fn put[T + 'a](d &mut &'a R, x T) {
*d = x; // Ok: what T holds lasts 'a
}
fn pick[T + 'a](x T, y &'a R) &'a R {
x; // Ok
}
fn putAny[T](d &mut &'a R, x T) {
*d = x; // Error 1064, once a call instances it: Stored where 'd' points, the value
} // carries the borrow the caller lent through 'x', whose lifetime is not one held there
A type argument holding no borrow meets every bound. One holding borrows meets it where each of them is known to last at least 'a: by where it comes from (a global lasts every lifetime, and a value of the caller's own lasts no lifetime of its callee's signature), by a where clause, or by what the caller's types imply. A call carries what an argument for T holds wherever 'a flows, so a borrow too short for where it was put is refused where it is used too late, and a caller naming its own lifetimes is held to its order:
fn caller(d &mut &'x R, v &'y R) {
put(d, v); // Error 1085: Call could store the borrow the caller lent through 'v' where 'd'
} // points, which holds no borrow of its lifetime
fn ordered(d &mut &'x R, v &'y R) where 'y >= 'x {
put(d, v); // Ok
}
fn main() {
let g = new R(1);
var d = &g;
{
let short = new R(2); // Error 1168: 'short', declared here, goes out of scope while 'd' still
put(&mut d, &short); // holds a borrow of it
}
d.n;
}
T + 'static says T holds no borrow but of a global: inside, a value of T may be stored in a global or returned as 'static, and a call is refused an argument for it carrying any borrow that is not global, whether written as a borrow or held inside a value passed by value. A function is still instanced once per type argument, never per lifetime: the bound is the parameter's, not the use's. A type argument inferred from a call's arguments is instanced as the same type written would be, with no lifetime of the call: bounded or not, the instance's body holds what a parameter of type T lends as the caller's, and is checked once for every call. The bound is also what lets a parameter that asks for Sendable take a borrow that is imm, opaq or uni: [A Sendable + 'static], as thread.start writes it (Race condition safety). Not built yet: a + 'a bound on a generic type's parameter, struct Bx[T + 'a] (Error 1249: A lifetime bound on a generic type's parameter is not built).
On a virtual reference. &<Trait + 'a, or &<'r Trait + 'a with its own lifetime: the value it points at holds only borrows lasting at least 'a, which is at least 'r (an implied bound). The type holds 'a as well as its own lifetime, so wherever it is stored and read back out, what it carries goes where 'a borrows may. A value coerced to it, returned or stored through a parameter, may hold only borrows known to last 'a; a parameter &<Trait + 'static is handed only a value holding global borrows. A virtual reference with no bound is checked as before:
trait Show { pub fn show(self &) i64; }
struct H['b] is Show { pub r &'b i64; pub fn show(self &) i64 { *r; } }
fn keep(slot &mut &<'r Show + 'a, h &'r H['b]) where 'b >= 'a {
*slot = h; // Ok: what h holds lasts 'a
}
fn leak(slot &mut &<'r Show + 'a, h &'r H['b]) {
*slot = h; // Error 1249: A virtual reference bounded by 'a points at a value whose borrows
} // all last 'a, but this one holds the borrow the caller lent through 'h'
fn view(h &'r H['b]) &<'r Show + 'b {
h; // Ok
}
An owning virtual reference, Uni[Trait] or Rc[Trait], names no lifetime, so it may outlive any borrow: it is bounded by 'static. A value made one, where it is declared, assigned, passed or returned, may hold only global borrows: a borrow of a global, one the caller lent through a 'static parameter or a type parameter bounded by 'static, or none at all. Not built yet: a bound of another lifetime on an owning virtual reference.
struct H is Show { pub r &R; pub fn show(self &) i64 { r.n; } }
fn boxit(h H) Uni[Show] {
new Uni[H](h); // Error 1249: An owning virtual reference names no lifetime, so it may outlive
} // any borrow ... This one holds the borrow the caller lent through 'h'
fn boxGlobal(r &'static R) Uni[Show] {
new Uni[H](new H(r)); // Ok: what it holds is global
}
fn boxAny[T Show + 'static](x T) Uni[Show] {
new Uni[T](x); // Ok
}
One more kind of lifetime is not a scope at all: the invariant lifetime behind a dynamic arena, which ties a reference to the value that made it rather than to a stretch of code. It is with Arenas, where it is used.
You can now return and store borrows across calls, give a struct the lifetimes of what it borrows, and bound a generic by them. That completes borrowing, apart from one subtle case: a borrow into something that can change its shape, like a list that grows.