In Rust docs, we see this example:
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
And explanation looks like this:
The function signature now tells Rust that for some lifetime 'a, the
function takes two parameters, both of which are string slices that
live at least as long as lifetime 'a. The function signature also
tells Rust that the string slice returned from the function will live
at least as long as lifetime 'a. In practice, it means that the
lifetime of the reference returned by the longest function is the same
as the smaller of the lifetimes of the references passed in
Note the words after "in practice". It mentions that:
In practice, it means that the
lifetime of the reference returned by the longest function is the same
as the smaller of the lifetimes of the references passed in
I don't understand why in practice, it means that lifetime of the returned is the same as the smaller of those 2 parameter's lifetimes. Is this something I need to memorize or what ? We can clearly say that parameters and returned values all have 'a same specifier. Why does Rust think that this means returned value should have smaller lifetime of those 2 passed ?
Why does rust think that this means returned value should have SMALLER lifetime of those 2 passed ?
Because that's the only thing that makes sense. Imagine this situation:
let a = "foo"; // &'static str
let s = "bar".to_string(); // String
let b = s.as_str(); // &str (non-static, borrows from s)
let longest = longest(a, b);
The lifetime of a is 'static, i.e. a lasts as long as the program. The lifetime of b is shorter than that, as it's tied to the lifetime of the variable s. But longest only accepts one lifetime!
What Rust does is compute a lifetime that is an intersection of the 'static lifetime of a and the tied-to-s lifetime of b, and uses that as the lifetime of (this invocation of) longest(). If such a lifetime cannot be found, you get a borrow checking error. If it can be found, it's no longer than the shortest source lifetime.
In the above case, the intersection of 'static and the lifetime tied to s is the lifetime tied to s, so that's what's used for the lifetime 'a in longest().
Related
I'm trying to understand why Rust makes a type have 'static lifetime.
Please take a look at this code:
let tcp_tx_buffer = TcpSocketBuffer::new(vec![0; 128]);
VSCode tells me that the type of tcp_tx_buffer is
smoltcp::storage::RingBuffer<'static, u8>
but if we find the new method on RingBuffer:
pub fn new<S>(storage: S) -> RingBuffer<'a, T>
where S: Into<ManagedSlice<'a, T>>,
{
RingBuffer {
storage: storage.into(),
read_at: 0,
length: 0,
}
}
there's no 'static lifetime on the return. In fact the lifetime is the same as the input, 'a. If let tcp_tx_buffer were outside main, I'd guess it's static, but it has its own scope. Or does Rust consider main to have a 'static lifetime?
The signature on new says that it returns a RingBuffer which carries a specific lifetime; it is defined to be the same lifetime as the one carried by whatever the storage-parameter returns as part of ManagedSlice when Into<ManagedSlice> is called on it. That is, the storage-parameter gets to decide the lifetime carried by the RingBuffer-value.
You are passing an owned Vec into TcpSocketBuffer::new(). An owned Vec which does not contain reference-types is itself 'static. TcpSocketBuffer can (due to its implementation) Into<ManagedSlice>, where ManagedSlice carries the lifetime original Vec's lifetime 'static. This is where 'static comes from.
It may be helpful when thinking about 'static that this lifetime does not mean that the value has to live forever. It just means that the value can be made to live forever. This is true for all values which do not contain references that themselves have a lifetime shorter than 'static. For example, a String::new()is 'static because it can be made to live as long as we want (simply by not dropping it). A Foo<'a> { bar: &'a str } can only be made to live for as long as 'a, because Foo contains a reference which may be shorter than 'static.
From the moment of creation, your owned Vec can be made to live for as long as we want and this property is carried through to RingBuffer.
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.
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 does this code compile?
#[derive(Debug)]
pub struct Foo<'a> {
pub x: &'a [&'a str],
}
fn main() {
let s = "s".to_string();
let t: &str = s.as_ref();
{
let v = vec![t];
let foo = Foo { x: &v[..] };
println!("{:?}", &foo);
}
}
In my Foo struct, my x field contains a slice of &strs. My understanding of these lifetime labels is that the slice and the individual &strs have the same lifetime.
However, in my example the &str t (in the outer block) does not have the same lifetime as the container slice (in the inner block).
My understanding of these lifetime labels is that the slice and the individual &strs have the same lifetime.
This is a common misconception. It really means that there has to be a lifetime that applies to both. When instantiated, the Foo struct's 'a corresponds to the lines of the inner block after let v = vec![t]; as that is a lifetime that both variables share.
If this flexibility didn't exist, lifetimes would be super painful to use. Variables defined on two lines have different actual lifetimes (the one defined first outlives the one defined second). If the lifetimes had to actually match, we'd always have to define all the variables on the same line!
Some more detailed information is available in RFC #738 — Variance.
The syntax 'a is actually used in two different situations:
labeling a loop
indicating a bound
The first situation, labeling a loop:
fn main() {
'a: loop {
println!("{}", 3);
break 'a;
}
}
Here, 'a clearly delineates the lifetime of the loop body, and allows breaking from multiple layers of loops in one fell swoop.
The second, and more similar situation, is using 'a to represent a bound:
fn<'a> susbtr(haystack: &'a str, offset: usize) -> &'a str;
In this case, the lifetime 'a does not represent the actual lifetime of the variable, it represents a bound on the lifetime of the referenced variable and allows tying together the bounds of various variables.
Note that the caller and callee interpret the bound differently:
from the perspective of the caller, 'a is an upper-bound, a promise that the return value will live at least as long as the parameter (maybe longer, no guarantee)
from the perspective of the callee (ie, substr), 'a is a lower-bound, a check that any returned value must live at least as long as the parameter (maybe longer, not necessary)
We can have variance since the bound does not represent the actual lifetime, when a single bound is used for multiple lifetimes, the compiler will simply deduce the lowest/highest bound that makes sense for the situation:
the caller gets the lowest upper-bound feasible (ie, the least guarantee)
the caller gets the highest lower-bound feasible (ie, the least constraint)
For example:
fn<'b> either(one: &'b str, two: &'b str, flag: bool) -> &'b str {
if flag { one } else { two }
}
can be called with:
fn<'a> call(o: &'a str, flag: bool) -> &'a str {
either(o, "Hello, World", flag)
}
Here, the lifetime of o is unknown (some 'a) whilst the lifetime of "Hello, World" is known ('static), 'static is by definition the greater of the lifetimes (it lives for all the program).
the caller of call only knows that the return value lives at least as long as o
call must guarantee this, it supplies o and "Hello, World" to either where 'b is deduced to be the lowest bound between 'a and 'static (thus 'a)
either simply must return something that lives as long as either one of its arguments; it's unaware that their lifetime may differ, and cares not
I feel dumb for having browsed the marker section of the Rust documentation and the Wikipedia articles about subtyping and variance multiple times without it improving my understanding of the lifetimes subtyping relation.
I think I'm just used to the "typical OOP-style" subtying relations like "Cat <: Animal" meaning "Cat is a subtype of Animal" where "S is a subtype of T" means "any term S can be safely used in a context where a term of type T is expected". So far so good.
But how does this apply to lifetimes? The way it is defined right now in Rust is apparently(*)
(#1) 'a <: 'b <=> lifetime a is no longer than lifetime b.
And you might think "Of course that's what it means!" possibly because <: looks similar to the less than operator or possibly because "sub" makes you think of subsets and a shorter lifespan is certainly a subset of a longer lifespan. But is 'a really a subtype of 'b if 'a is no longer than 'b? Let's try to apply Wikipedia's definition of the subtype relation:
(#2) 'a <: 'b <=> lifetime a can be safely used in a context where lifetime b is expected.
The problem I have is that I'm not able to reconcile this. How do you get from #2 to #1? Because to me, this seems like a contradiction... If you expect something to be alive for at least b and you have something with a lifetime a that's shorter than b, you obviously cannot use it in that context where something with a lifetime b is required, can you? Is it just me or did we get the subtyping relation for lifetimes wrong?
Edit: (*) According to Ms2ger in the #rust IRC channel this is the case. It also fits with the documentation on the contravariant lifetime marker which is used in the Items iterator.
Edit2: The ContravariantLifetime and CovariantLifetime markers have been removed. We now have PhantomData as a replacement in the marker module.
Disclaimer: I am not exactly a CS guru, so this answer will focus on practical concepts and I will not even attempt to link it to theoretical concepts lest I make a mess of things.
I think that the issue is trying to apply the subtyping concept to something which is not a type.
'a is a lifetime
&'a T is a type
You can compare &'a T and &'b U and see whether they obey a subtyping relationship, but you cannot establish a subtyping relationship with two lifetimes in the abstract because:
sometimes, in order to be substituable, the new lifetime must be greater than the replaced lifetime.
sometimes, in order to be substituable, the new lifetime must be smaller than the replaced lifetime.
We can check this through two simple examples.
The first example is maybe the easiest: a lifetime can be substituted if it is greater!
// Using a lifetime as a bound
struct Reference<'a, T>
where T: 'a
{
data: &'a T
}
fn switch<'a, 'b, T>(r: &mut Reference<'a, T>, new: &'b T)
where 'b: 'a
{
r.data = new;
}
Here, the compiler only allows the substitution if 'b is at least as great as 'a which is expressed by the lifetime bound 'b: 'a. This is because Rust abhors dangling references, and thus a container may only contain references to objects that will outlive it.
When used as a guarantee, a greater lifetime is a subtype of a lesser lifetime and can be substituted in its stead. This hints as mentioned by #aturon, that in this usage 'static is a subtype of all lifetimes.
The second example is a tad trickier: a lifetime can be substituted if it is lesser!
Let's start with the following:
struct Token;
fn restrict<'a, 'b, T>(original: &'a T, _: &'b Token) -> &'b T
where 'a: 'b
{
original
}
The following usage is correct:
fn main() {
let i = 4;
{
let lesser = Token;
let k = restrict(&i, &lesser);
println!("{}", k);
}
}
And our previous demonstration said that we can substitute a greater lifetime instead of a lesser one:
fn main() {
let greater = Token;
let j; // prevent unification of lifetimes
{
let i = 4;
j = restrict(&i, &greater);
}
println!("{}", j);
}
error: `i` does not live long enough
j = restrict(&i, &greater);
When used as a constraint, a lesser lifetime is a subtype of a greater lifetime and can be substituted in its stead. In this usage, 'static is a supertype of all lifetimes.
Therefore, there is no single subtyping relationship between lifetimes because they serve two radically opposite purposes!
To recapitulate:
when used as a guarantee: greater <: lesser
when used as a constraint: lesser <: greater
Note: some lifetime can plausibly act both as a guarantee AND a constraint at the same time.