Difference between "p: &'a i32" and "p: &'static i32" lifetime in Rust? - rust

I started learning Rust a few days back.
This is an extract from the famous book Programming Rust by Jim Blandy.
For the code
fn g<'a>(p: &'a i32) { ... }
let x = 10;
g(&x);
The book says
Rust Choose the smallest possible lifetime for &x, that of the call to g. This meets all constraints: it doesn't outlive x, and encloses the entire call to g. So code must muster.
Q1. What is meant by the smallest possible lifetime for &x?
For the code
fn f(p: &'static i32) { ... }
let x = 10;
f(&x);
Q2. Why does this code fail? According to my understanding, &'static is used for static global variables which live for the full program. link

A 'static lifetime is a special concept. It specifies that the variable referenced by this needs to exist for the entire lifetime of the program. Using this is a rare case, and requires even rarer precautions to fulfill.
In practice, a &'static reference may only happen in two cases:
A const declaration
A static declaration
Both effectively accomplish the same thing, in different ways; the differences aren't important to this question, however. In both cases, the outcome is a variable that is available for the entire lifetime of the program and will not be relocated, thus guaranteeing &'static if borrowed.
Now that we've covered this, let's cover both of your questions.
Q1. What is meant by the smallest possible lifetime for &x?
When you define a function as fn g<'a>(p: &'a i32) { ... }, you are requiring p to be valid for a lifetime 'a; this lifetime is determined by the compiler so that 'a is the smallest possible. If the reference is never used outside of the function scope, 'a will be the lifetime of execution of that function, for example. If you use or reference this borrow outside of the function, the lifetime will (evidently) be larger.
The definition of "smallest possible" is simple: the compiler will infer the lifetime based from the time you start that reference, to the last time you use that reference. Dependent borrows also count, and this typically comes back to bite people when dealing with collections.
The reason it is the smallest possible is so that you don't run into crazy situations where you don't have a borrow but it is borrowed anyway; this typically happens when you try to provide your own, incorrect, lifetime hints. There are plenty of cases where it is usually best to let the compiler decide; the other case is struct implementations such as the following:
struct Foo<'a> {
item: &'a u32
}
impl<'a> Foo<'a> {
pub fn compare<'b>(&self, other: &'b u32) {
...
}
}
A common fault in situations like this is to describe other to the compiler as 'a, not defining the second 'b lifetime, and thus (accidentally) requiring other to be borrowed for the lifetime of the struct itself.
Q2. Why does this code fail? According to my understanding, &'static is used for static global variables which live for the full program.
let x = 10;
This assignment does not have a 'static lifetime. It has an anonymous lifetime defined as less than 'static, because it is not strictly defined as global. The only way to get a 'static borrow on anything is if that source element is defined as const or static.
You can convince yourself of this with this snippet (playground):
fn f(p: &'static i32) {
println!("{}", p)
}
const FOO:i32 = 3;
static BAR:i32 = 4;
fn main() {
f(&FOO); // Works
f(&BAR); // Also works
}
f(&x);

A 'static lifetime requirement on a reference requires this argument to be declared for the global lifetime of the program, but x cannot fulfill this condition as it is declared midway through execution.
To be able to use this, declare x as const or static so its lifetime will be 'static and the code will work fine.

Related

Why is a lifetime needed when implementing From<&[u8]>

I'm trying to have a MyType that supports a From<&[u8]> trait, but I'm running into "lifetime problems":
Here's a minimally viable example:
struct MyType {
i: i32
}
impl MyType {
fn from_bytes(_buf: &[u8]) -> MyType {
// for example...
MyType { i: 3 }
}
}
impl From<&[u8]> for MyType {
fn from(bytes: &[u8]) -> Self {
MyType::from_bytes(bytes)
}
}
fn do_smth<T>() -> T where T: From<&[u8]>
{
// for example...
let buf : Vec<u8> = vec![1u8, 2u8];
T::from(buf.as_slice())
}
(...and here's a Rust playground link)
For reasons I cannot understand, the Rust compiler is telling me:
error[E0637]: `&` without an explicit lifetime name cannot be used here
--> src/lib.rs:17:36
|
17 | fn do_smth<T>() -> T where T: From<&[u8]>
| ^ explicit lifetime name needed here
I'm not an expert on lifetimes and I don't understand why this piece of code needs one. What would be the best way to fix this?
Might Rust be thinking that the type T could be a &[u8] itself? But, in that case, the lifetime should be inferred to be the same as the input to From::<&[u8]>::from(), no?
One fix I was given was to do:
fn do_smth<T>() -> T where for<'a> T: From<&'a [u8]>
{
// for example...
let buf : Vec<u8> = vec![1u8, 2u8];
T::from(buf.as_slice())
}
...but I do not understand this fix, nor do I understand why lifetimes are needed in the first place.
Rust first wants you to write:
fn do_smth<'a, T>() -> T
where
T: From<&'a [u8]>,
{
// for example...
let buf: Vec<u8> = vec![1u8, 2u8];
T::from(&buf)
}
where you make explicit that this function can be called for any lifetime 'a and any type T such that T implements From<&'a [u8]>.
But Rust then complains:
error[E0597]: `buf` does not live long enough
--> src/lib.rs:24:13
|
18 | fn do_smth<'a, T>() -> T
| -- lifetime `'a` defined here
...
24 | T::from(&buf)
| --------^^^^-
| | |
| | borrowed value does not live long enough
| argument requires that `buf` is borrowed for `'a`
25 | }
| - `buf` dropped here while still borrowed
You promised that this function could work with any lifetime, but this turns out to not be true, because in the body of the function you create a fresh reference to the local Vec which has a different lifetime, say 'local. Your function only works when 'a equals 'local, but you promise that it also works for all other lifetimes. What you need is a way to express that these lifetimes are the same, and the only way I think that is possible is by changing the local reference to an argument:
fn do_smth<'a, T>(buf: &'a [u8]) -> T
where
T: From<&'a [u8]>,
{
T::from(buf)
}
And then it compiles.
If instead of the function promising it can work with any lifetime, you want to make the caller promise it can work with any lifetime, you can instead use HRTBs to make the caller promise it.
fn do_smth<T>() -> T
where
for<'a> T: From<&'a [u8]>,
{
// for example...
let buf: Vec<u8> = vec![1u8, 2u8];
T::from(&buf)
}
Now, since you can use any lifetime, a local one also works and the code compiles.
Lifetimes represent a "duration" (metaphorically), or, more pragmatically, a scope, in which a variable is valid. Outside of one's lifetime, the variable should be considered as having been freed from memory, even though you haven't done that explicitly, because that's how Rust manages memory.
It becomes a bit more complex when Rust tries to ensure that, once a variable is done for, no other parts of the code that could have had access to that variable still have access. These shared accesses are called borrows, and that's why borrows have lifetimes too. The main condition Rust enforces on them is that a borrow's lifetime is always shorter (or within, depending on how you see it) than its original variable, ie. you can't share something for more time than you actually own it.
Rust therefore enforces all borrows (as well as all variables, really) to have an established lifetime at compile-time. To lighten things, Rust has default rules about what a lifetime should be if it was not explicitly defined by the user, that is, when you talk about a type that involves a lifetime, Rust let's you not write that lifetime explicitly under certain conditions. However, this is not a "lifetime inference", in the sense of inferring types: Rust will not try to make sense out of explicit lifetimes, it's a lot less smart about it. In particular, this lifetime explicitation can fail, in the sense that Rust will not be able to figure out the right lifetime it has to assign even though it was possible to find out that worked.
Back to business: your first error simply stems from the fact that Rust has no rule to make a lifetime if it wasn't provided in the position pointed out by the error. As I said, Rust won't try to infer what the right lifetime would be, it simply checks whether not explicitly putting a lifetime there implicitly means something or not. So, you simply need to put one.
Your first reflex might be to make your function generic over the missing lifetime, which is often the right thing to do (and even the only possible action), that is, do something like that:
fn do_smth<'a, T>() -> T
where
T: From<&'a [u8]>
{
// for example...
let buf : Vec<u8> = vec![1, 2];
T::from(buf.as_slice())
}
What this means is that do_smth is generic over the lifetime 'a, just like it is generic over the type T. This has two consequences:
Rust will proceed to a monomorphisation of your function for each call, meaning it will actually provide a concrete implementation of your function for each type T and each lifetime 'a that is required. In particular, it will automatically find out what is the right lifetime. This might seem contradictory with what I said earlier, about Rust not inferring lifetimes. The difference is that type inference and monomorphisation, although similar, are not the same step, and so the compiler does not work lifetimes in the same way. Don't worry about this until you have understood the rest.
The second consequence, which is a bit disastrous, is that your function exposes the following contract: for any type T, and any lifetime 'a, such that T: From<&'a [u8]>, do_smth can produce a type T. If you think about it, it means that even if T only implements From<&'a [u8]> for a lifetime 'a that is already finished (or, if you see lifetimes as scopes, for a lifetime 'a that is disjoint from do_smth's scope), you can produce an element of type T. This is not what you actually meant: you don't want the caller to give you an arbitrary lifetime. Instead, you know that the lifetime of the borrow of the slice is the one you chose it to be, within your function (because you own the underlying vector), and you want that the type T to be buildable from that slice. That is, you want T: From<&'a [u8]> for a 'a that you have chosen, not one provided by the caller.
This last point should make you understand why the previous snippet of code is unsound, and won't compile. Your function should not take a lifetime as argument, just a type T with certain constraints. But then, how do you encode the said conditions? That's where for<'a> comes into play. If you have a type T such that T: for<'a> From<&'a [u8]>, it means that for all 'a, T: From<&'a [u8]>. In particular, it is true for the lifetime of your slice. This is why the following works
fn do_smth<T>() -> T
where
T: for<'a> From<&'a [u8]>
{
// for example...
let buf: Vec<u8> = vec![1, 2];
T::from(buf.as_slice())
}
Note that, as planned, this version of do_smth is not generic over a lifetime, that is, the caller does not provide a lifetime to the function.

Can I coerce a lifetime parameter to a shorter lifetime (soundly) even in the presence of `&mut T`?

I'm trying to make a tree with parent pointers in Rust. A method on the node struct is giving me lifetime issues. Here's a minimal example, with lifetimes written explicitly so that I can understand them:
use core::mem::transmute;
pub struct LogNode<'n>(Option<&'n mut LogNode<'n>>);
impl<'n> LogNode<'n> {
pub fn child<'a>(self: &'a mut LogNode<'n>) -> LogNode<'a> {
LogNode(Some(self))
}
pub fn transmuted_child<'a>(self: &'a mut LogNode<'n>) -> LogNode<'a> {
unsafe {
LogNode(Some(
transmute::<&'a mut LogNode<'n>, &'a mut LogNode<'a>>(self)
))
}
}
}
(Playground link)
Rust complains about child...
error[E0495]: cannot infer an appropriate lifetime for lifetime parameter 'n due to conflicting requirements
...but it's fine with transmuted_child.
I think I understand why child won't compile: the self parameter's type is &'a mut LogNode<'n> but the child node contains an &'a mut LogNode<'a>, and Rust doesn't want to coerce LogNode<'n> to LogNode<'a>. If I change the mutable references to shared references, it compiles fine, so it sounds like the mutable references are a problem specifically because &mut T is invariant over T (whereas &T is covariant). I guess the mutable reference in LogNode bubbles up to make LogNode itself invariant over its lifetime parameter.
But I don't understand why that's true—intuitively it feels like it's perfectly sound to take LogNode<'n> and shorten its contents' lifetimes by turning it into a LogNode<'a>. Since no lifetime is made longer, no value can be accessed past its lifetime, and I can't think of any other unsound behavior that could happen.
transmuted_child avoids the lifetime issue because it sidesteps the borrow checker, but I don't know if the use of unsafe Rust is sound, and even if it is, I'd prefer to use safe Rust if possible. Can I?
I can think of three possible answers to this question:
child can be implemented entirely in safe Rust, and here's how.
child cannot be implemented entirely in safe Rust, but transmuted_child is sound.
child cannot be implemented entirely in safe Rust, and transmuted_child is unsound.
Edit 1: Fixed a claim that &mut T is invariant over the lifetime of the reference. (Wasn't reading the nomicon right.)
Edit 2: Fixed my first edit summary.
The answer is #3: child cannot be implemented in safe Rust, and transmuted_child is unsound¹. Here's a program that uses transmuted_child (and no other unsafe code) to cause a segfault:
fn oops(arg: &mut LogNode<'static>) {
let mut short = LogNode(None);
let mut child = arg.transmuted_child();
if let Some(ref mut arg) = child.0 {
arg.0 = Some(&mut short);
}
}
fn main() {
let mut node = LogNode(None);
oops(&mut node);
println!("{:?}", node);
}
short is a short-lived local variable, but since you can use transmuted_child to shorten the lifetime parameter of the LogNode, you can stuff a reference to short inside a LogNode that should be 'static. When oops returns, the reference is no longer valid, and trying to access it causes undefined behavior (segfaulting, for me).
¹ There is some subtlety to this. It is true that transmuted_child itself does not have undefined behavior, but because it makes other code such as oops possible, calling or exposing it may make your interface unsound. To expose this function as part of a safe API, you must take great care to not expose other functionality that would let a user write something like oops. If you cannot do that, and you cannot avoid writing transmuted_child, it should be made an unsafe fn.
To understand why the immutable version works and the mutable version is unsound (as written), we have to discuss subtyping and variance.
Rust mostly doesn't have subtyping. Values typically have a unique type. One place where Rust does have subtyping, however, is with lifetimes. If 'a: 'b (read 'a is longer than 'b), then, for example, &'a T is a subtype of &'b T, intuitively because longer lifetimes can be treated as if they were shorter.
Variance is how subtyping propagates. If A is a subtype of B, and we have a generic type Foo<T>, Foo<A> might be a subtype of Foo<B>, vice versa, or neither. In the first case, where the direction of subtyping stays the same, Foo<T> is said to be covariant with respect to T. In the second case, where the direction reverses, it's said to be contravariant, and in the third case, it's said to be invariant.
For this case, the relevant types are &'a T and &'a mut T. Both are covariant in 'a (so references with longer lifetimes can be coerced to references with shorter lifetimes). &'a T is covariant in T, but &'a mut T is invariant in T.
The reason for this is explained in the Nomicon (linked above), so I'll just show you the (somewhat simplified) example given there. Trentcl's code is a working example of what goes wrong if &'a mut T is covariant in T.
fn evil_feeder(pet: &mut Animal) {
let spike: Dog = ...;
// `pet` is an Animal, and Dog is a subtype of Animal,
// so this should be fine, right..?
*pet = spike;
}
fn main() {
let mut mr_snuggles: Cat = ...;
evil_feeder(&mut mr_snuggles); // Replaces mr_snuggles with a Dog
mr_snuggles.meow(); // OH NO, MEOWING DOG!
}
So why does the immutable version of child work, but not the mutable version? In the immutable version, LogNode contains an immutable reference to a LogNode, so by covariance in both the lifetime and the type parameter, LogNode is covariant in its lifetime parameter. If 'a: 'b, then LogNode<'a> is a subtype of LogNode<'b>.
We have self: &'a LogNode<'n>, which implies 'n: 'a (otherwise this borrow would outlast the data in LogNode<'n>). Thus, since LogNode is covariant, LogNode<'n> is a subtype of LogNode<'a>. Furthermore, covariance in immutable references again allows &'a LogNode<'n> to be a subtype of &'a LogNode<'a>. Thus, self: &'a LogNode<'n> can be coerced to &'a LogNode<'a> as needed for the return type in child.
For the mutable version, LogNode<'n> isn't covariant in 'n. The variance here comes down to the variance of &'n mut LogNode<'n>. But since there's a lifetime in the "T" part of the mutable reference here, the invariance of mutable references (in T) implies that this must also be invariant.
This all combines to show that self: &'a mut LogNode<'n> can't be coerced to &'a mut LogNode<'a>. So the function doesn't compile.
One solution to this is to add the lifetime bound 'a: 'n, though as noted above, we already have 'n: 'a, so this forces the two lifetimes to be equal. That may or may not work with the rest of your code, so take it with a grain of salt.

Do non-reference types always satisfy a lifetime of 'static?

I'm trying to understand why the code below compiles. I wasn't expecting to be able to construct Wrapper<String> since T: 'static and runtime-allocated strings don't live for the entire lifetime of the program.
I think the reason this is allowed is because I'm setting T to a non-reference type (String). When I use a &str, or a struct containing a reference, the compiler issues the error I'd expect.
However, I haven't been able to find anything in the Rust docs that confirms my hypothesis, so maybe I don't fully understand the rules. Will all non-reference types satisfy the 'static lifetime bound on Wrapper<T>, or are there some that will fail?
use rand::Rng;
struct Wrapper<T>
where
T: 'static,
{
value: T,
}
fn wrap_string() -> Wrapper<String> {
// Use rng to avoid construcing string at compile time
let mut rng = rand::thread_rng();
let n: u8 = rng.gen();
let text = format!("The number is {}", n);
Wrapper { value: text }
}
fn main() {
let wrapped = wrap_string();
std::mem::drop(wrapped);
}
From the Background section of RFC 2093:
[...] in order for a reference type &'a T to be "well formed" (valid),
the compiler must know that the type T "outlives" the lifetime 'a --
meaning that all references contained in the type T must be valid for
the lifetime 'a. So, for example, the type i32 outlives any lifetime,
including 'static, since it has no references at all.
So I'd say the answer to your question is: yes, any type which has no references (or which only contains static references) satisfies the 'static bound.
Side note: according to that RFC, bounds like T: 'static and T: 'a are known as outlives requirements.
You can think of a type bound T: 'x as "Instances of T cannot suddenly become invalid because something that lives shorter than 'x was dropped.". However, this does not affect how long the instance of T itself lives.
So, a reference becomes invalid if the referenced thing is dropped. Which means that the referenced thing must live at least as long as 'x - for the entire run of the program in the case of 'static.
But something that own all its data - an i32 or a String for example - never becomes invalid because something else is dropped. An integer is good until it is dropped itself. So it satisfies the 'static bound.

Why are explicit lifetimes needed in Rust?

I was reading the lifetimes chapter of the Rust book, and I came across this example for a named/explicit lifetime:
struct Foo<'a> {
x: &'a i32,
}
fn main() {
let x; // -+ x goes into scope
// |
{ // |
let y = &5; // ---+ y goes into scope
let f = Foo { x: y }; // ---+ f goes into scope
x = &f.x; // | | error here
} // ---+ f and y go out of scope
// |
println!("{}", x); // |
} // -+ x goes out of scope
It's quite clear to me that the error being prevented by the compiler is the use-after-free of the reference assigned to x: after the inner scope is done, f and therefore &f.x become invalid, and should not have been assigned to x.
My issue is that the problem could have easily been analyzed away without using the explicit 'a lifetime, for instance by inferring an illegal assignment of a reference to a wider scope (x = &f.x;).
In which cases are explicit lifetimes actually needed to prevent use-after-free (or some other class?) errors?
The other answers all have salient points (fjh's concrete example where an explicit lifetime is needed), but are missing one key thing: why are explicit lifetimes needed when the compiler will tell you you've got them wrong?
This is actually the same question as "why are explicit types needed when the compiler can infer them". A hypothetical example:
fn foo() -> _ {
""
}
Of course, the compiler can see that I'm returning a &'static str, so why does the programmer have to type it?
The main reason is that while the compiler can see what your code does, it doesn't know what your intent was.
Functions are a natural boundary to firewall the effects of changing code. If we were to allow lifetimes to be completely inspected from the code, then an innocent-looking change might affect the lifetimes, which could then cause errors in a function far away. This isn't a hypothetical example. As I understand it, Haskell has this problem when you rely on type inference for top-level functions. Rust nipped that particular problem in the bud.
There is also an efficiency benefit to the compiler — only function signatures need to be parsed in order to verify types and lifetimes. More importantly, it has an efficiency benefit for the programmer. If we didn't have explicit lifetimes, what does this function do:
fn foo(a: &u8, b: &u8) -> &u8
It's impossible to tell without inspecting the source, which would go against a huge number of coding best practices.
by inferring an illegal assignment of a reference to a wider scope
Scopes are lifetimes, essentially. A bit more clearly, a lifetime 'a is a generic lifetime parameter that can be specialized with a specific scope at compile time, based on the call site.
are explicit lifetimes actually needed to prevent [...] errors?
Not at all. Lifetimes are needed to prevent errors, but explicit lifetimes are needed to protect what little sanity programmers have.
Let's have a look at the following example.
fn foo<'a, 'b>(x: &'a u32, y: &'b u32) -> &'a u32 {
x
}
fn main() {
let x = 12;
let z: &u32 = {
let y = 42;
foo(&x, &y)
};
}
Here, the explicit lifetimes are important. This compiles because the result of foo has the same lifetime as its first argument ('a), so it may outlive its second argument. This is expressed by the lifetime names in the signature of foo. If you switched the arguments in the call to foo the compiler would complain that y does not live long enough:
error[E0597]: `y` does not live long enough
--> src/main.rs:10:5
|
9 | foo(&y, &x)
| - borrow occurs here
10 | };
| ^ `y` dropped here while still borrowed
11 | }
| - borrowed value needs to live until here
The lifetime annotation in the following structure:
struct Foo<'a> {
x: &'a i32,
}
specifies that a Foo instance shouldn't outlive the reference it contains (x field).
The example you came across in the Rust book doesn't illustrate this because f and y variables go out of scope at the same time.
A better example would be this:
fn main() {
let f : Foo;
{
let n = 5; // variable that is invalid outside this block
let y = &n;
f = Foo { x: y };
};
println!("{}", f.x);
}
Now, f really outlives the variable pointed to by f.x.
Note that there are no explicit lifetimes in that piece of code, except the structure definition. The compiler is perfectly able to infer lifetimes in main().
In type definitions, however, explicit lifetimes are unavoidable. For example, there is an ambiguity here:
struct RefPair(&u32, &u32);
Should these be different lifetimes or should they be the same? It does matter from the usage perspective, struct RefPair<'a, 'b>(&'a u32, &'b u32) is very different from struct RefPair<'a>(&'a u32, &'a u32).
Now, for simple cases, like the one you provided, the compiler could theoretically elide lifetimes like it does in other places, but such cases are very limited and do not worth extra complexity in the compiler, and this gain in clarity would be at the very least questionable.
If a function receives two references as arguments and returns a reference, then the implementation of the function might sometimes return the first reference and sometimes the second one. It is impossible to predict which reference will be returned for a given call. In this case, it is impossible to infer a lifetime for the returned reference, since each argument reference may refer to a different variable binding with a different lifetime. Explicit lifetimes help to avoid or clarify such a situation.
Likewise, if a structure holds two references (as two member fields) then a member function of the structure may sometimes return the first reference and sometimes the second one. Again explicit lifetimes prevent such ambiguities.
In a few simple situations, there is lifetime elision where the compiler can infer lifetimes.
I've found another great explanation here: http://doc.rust-lang.org/0.12.0/guide-lifetimes.html#returning-references.
In general, it is only possible to return references if they are
derived from a parameter to the procedure. In that case, the pointer
result will always have the same lifetime as one of the parameters;
named lifetimes indicate which parameter that is.
The case from the book is very simple by design. The topic of lifetimes is deemed complex.
The compiler cannot easily infer the lifetime in a function with multiple arguments.
Also, my own optional crate has an OptionBool type with an as_slice method whose signature actually is:
fn as_slice(&self) -> &'static [bool] { ... }
There is absolutely no way the compiler could have figured that one out.
As a newcomer to Rust, my understanding is that explicit lifetimes serve two purposes.
Putting an explicit lifetime annotation on a function restricts the type of code that may appear inside that function. Explicit lifetimes allow the compiler to ensure that your program is doing what you intended.
If you (the compiler) want(s) to check if a piece of code is valid, you (the compiler) will not have to iteratively look inside every function called. It suffices to have a look at the annotations of functions that are directly called by that piece of code. This makes your program much easier to reason about for you (the compiler), and makes compile times managable.
On point 1., Consider the following program written in Python:
import pandas as pd
import numpy as np
def second_row(ar):
return ar[0]
def work(second):
df = pd.DataFrame(data=second)
df.loc[0, 0] = 1
def main():
# .. load data ..
ar = np.array([[0, 0], [0, 0]])
# .. do some work on second row ..
second = second_row(ar)
work(second)
# .. much later ..
print(repr(ar))
if __name__=="__main__":
main()
which will print
array([[1, 0],
[0, 0]])
This type of behaviour always surprises me. What is happening is that df is sharing memory with ar, so when some of the content of df changes in work, that change infects ar as well. However, in some cases this may be exactly what you want, for memory efficiency reasons (no copy). The real problem in this code is that the function second_row is returning the first row instead of the second; good luck debugging that.
Consider instead a similar program written in Rust:
#[derive(Debug)]
struct Array<'a, 'b>(&'a mut [i32], &'b mut [i32]);
impl<'a, 'b> Array<'a, 'b> {
fn second_row(&mut self) -> &mut &'b mut [i32] {
&mut self.0
}
}
fn work(second: &mut [i32]) {
second[0] = 1;
}
fn main() {
// .. load data ..
let ar1 = &mut [0, 0][..];
let ar2 = &mut [0, 0][..];
let mut ar = Array(ar1, ar2);
// .. do some work on second row ..
{
let second = ar.second_row();
work(second);
}
// .. much later ..
println!("{:?}", ar);
}
Compiling this, you get
error[E0308]: mismatched types
--> src/main.rs:6:13
|
6 | &mut self.0
| ^^^^^^^^^^^ lifetime mismatch
|
= note: expected type `&mut &'b mut [i32]`
found type `&mut &'a mut [i32]`
note: the lifetime 'b as defined on the impl at 4:5...
--> src/main.rs:4:5
|
4 | impl<'a, 'b> Array<'a, 'b> {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
note: ...does not necessarily outlive the lifetime 'a as defined on the impl at 4:5
--> src/main.rs:4:5
|
4 | impl<'a, 'b> Array<'a, 'b> {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
In fact you get two errors, there is also one with the roles of 'a and 'b interchanged. Looking at the annotation of second_row, we find that the output should be &mut &'b mut [i32], i.e., the output is supposed to be a reference to a reference with lifetime 'b (the lifetime of the second row of Array). However, because we are returning the first row (which has lifetime 'a), the compiler complains about lifetime mismatch. At the right place. At the right time. Debugging is a breeze.
The reason why your example does not work is simply because Rust only has local lifetime and type inference. What you are suggesting demands global inference. Whenever you have a reference whose lifetime cannot be elided, it must be annotated.
I think of a lifetime annotation as a contract about a given ref been valid in the receiving scope only while it remains valid in the source scope. Declaring more references in the same lifetime kind of merges the scopes, meaning that all the source refs have to satisfy this contract.
Such annotation allow the compiler to check for the fulfillment of the contract.

Why does the lifetime name appear as part of the function type?

I believe that this function declaration tells Rust that the lifetime of the function's output is the same as the lifetime of it's s parameter:
fn substr<'a>(s: &'a str, until: u32) -> &'a str;
^^^^
It seems to me that the compiler only needs to know this(1):
fn substr(s: &'a str, until: u32) -> &'a str;
What does the annotation <'a> after the function name mean? Why does the compiler need it, and what does it do with it?
(1): I know it needs to know even less, due to lifetime elision. But this question is about specifying lifetime explicitly.
Let me expand on the previous answers…
What does the annotation <'a> after the function name mean?
I wouldn't use the word "annotation" for that. Much like <T> introduces a generic type parameter, <'a> introduces a generic lifetime parameter. You can't use any generic parameters without introducing them first and for generic functions this introduction happens right after their name. You can think of a generic function as a family of functions. So, essentially, you get one function for every combination of generic parameters. substr::<'x> would be a specific member of that function family for some lifetime 'x.
If you're unclear on when and why we have to be explicit about lifetimes, read on…
A lifetime parameter is always associated with all reference types. When you write
fn main() {
let x = 28374;
let r = &x;
}
the compiler knows that x lives in the main function's scope enclosed with curly braces. Internally, it identifies this scope with some lifetime parameter. For us, it is unnamed. When you take the address of x, you'll get a value of a specific reference type. A reference type is kind of a member of a two dimensional family of reference types. One axis is the type of what the reference points to and the other axis is a lifetime that is used for two constraints:
The lifetime parameter of a reference type represents an upper bound for how long you can hold on to that reference
The lifetime parameter of a reference type represents a lower bound for the lifetime of the things you can make the reference point to.
Together, these constraints play a vital role in Rust's memory safety story. The goal here is to avoid dangling references. We would like to rule out references that point to some memory region we are not allowed to use anymore because that thing it used to point to does not exist anymore.
One potential source of confusion is probably the fact that lifetime parameters are invisible most of the time. But that does not mean they are not there. References always have a lifetime parameter in their type. But such a lifetime parameter does not have to have a name and most of the time we don't need to mention it anyways because the compiler can assign names for lifetime parameters automatically. This is called "lifetime elision". For example, in the following case, you don't see any lifetime parameters being mentioned:
fn substr(s: &str, until: u32) -> &str {…}
But it's okay to write it like this. It's actually a short-cut syntax for the more explicit
fn substr<'a>(s: &'a str, until: u32) -> &'a str {…}
Here, the compiler automatically assigns the same name to the "input lifetime" and the "output lifetime" because it's a very common pattern and most likely exactly what you want. Because this pattern is so common, the compiler lets us get away without saying anything about lifetimes. It assumes that this more explicit form is what we meant based on a couple of "lifetime elision" rules (which are at least documented here)
There are situations in which explicit lifetime parameters are not optional. For example, if you write
fn min<T: Ord>(x: &T, y: &T) -> &T {
if x <= y {
x
} else {
y
}
}
the compiler will complain because it will interpret the above declaration as
fn min<'a, 'b, 'c, T: Ord>(x: &'a T, y: &'b T) -> &'c T { … }
So, for each reference a separate lifetime parameter is introduced. But no information on how the lifetime parameters relate to each other is available in this signature. The user of this generic function could use any lifetimes. And that's a problem inside its body. We're trying to return either x or y. But the type of x is &'a T. That's not compatible with the return type &'c T. The same is true for y. Since the compiler knows nothing about how these lifetimes relate to each other, it's not safe to return these references as a reference of type &'c T.
Can it ever be safe to go from a value of type &'a T to &'c T? Yes. It's safe if the lifetime 'a is equal or greater than the lifetime 'c. Or in other words 'a: 'c. So, we could write this
fn min<'a, 'b, 'c, T: Ord>(x: &'a T, y: &'b T) -> &'c T
where 'a: 'c, 'b: 'c
{ … }
and get away with it without the compiler complaining about the function's body. But it's actually unnecessarily complex. We can also simply write
fn min<'a, T: Ord>(x: &'a T, y: &'a T) -> &'a T { … }
and use a single lifetime parameter for everything. The compiler is able to deduce 'a as the minimum lifetime of the argument references at the call site just because we used the same lifetime name for both parameters. And this lifetime is precisely what we need for the return type.
I hope this answers your question. :)
Cheers!
What does the annotation <'a> after the function name mean?
fn substr<'a>(s: &'a str, until: u32) -> &'a str;
// ^^^^
This is declaring a generic lifetime parameter. It's similar to a generic type parameter (often seen as <T>), in that the caller of the function gets to decide what the lifetime is. Like you said, the lifetime of the result will be the same as the lifetime of the first argument.
All lifetime names are equivalent, except for one: 'static. This lifetime is pre-set to mean "guaranteed to live for the entire life of the program".
The most common lifetime parameter name is probably 'a, but you can use any letter or string. Single letters are most common, but any snake_case identifier is acceptable.
Why does the compiler need it, and what does it do with it?
Rust generally favors things to be explicit, unless there's a very good ergonomic benefit. For lifetimes, lifetime elision takes care of something like 85+% of cases, which seemed like a clear win.
Type parameters live in the same namespace as other types — is T a generic type or did someone name a struct that? Thus type parameters need to have an explicit annotation that shows that T is a parameter and not a real type. However, lifetime parameters don't have this same problem, so that's not the reason.
Instead, the main benefit of explicitly listing type parameters is because you can control how multiple parameters interact. A nonsense example:
fn better_str<'a, 'b, 'c>(a: &'a str, b: &'b str) -> &'c str
where
'a: 'c,
'b: 'c,
{
if a.len() < b.len() {
a
} else {
b
}
}
We have two strings and say that the input strings may have different lifetimes, but must both outlive the lifetime of the result value.
Another example, as pointed out by DK, is that structs can have their own lifetimes. I made this example also a bit of nonsense, but it hopefully conveys the point:
struct Player<'a> {
name: &'a str,
}
fn name<'p, 'n>(player: &'p Player<'n>) -> &'n str {
player.name
}
Lifetimes can be one of the more mind-bending parts of Rust, but they are pretty great when you start to grasp them.
The <'a> annotation just declares the lifetimes used in the function, exactly like generic parameters <T>.
fn subslice<'a, T>(s: &'a [T], until: u32) -> &'a [T] { \\'
&s[..until as usize]
}
Note that in your example, all lifetimes can be inferred.
fn subslice<T>(s: &[T], until: u32) -> &[T] {
&s[..until as usize]
}
fn substr(s: &str, until: u32) -> &str {
&s[..until as usize]
}
playpen example

Resources