I have the following struct:
struct PeekableRead<'a, R: Read> {
reader: &'a mut R,
peeked_octet: Option<u8>,
}
Which rustc does not like:
…:27:1: 30:2 error: the parameter type `R` may not live long enough [E0309]
…:27 struct PeekableRead<'a, R: Read> {
…:28 reader: &'a mut R,
…:29 peeked_octet: Option<u8>,
…:30 }
…:27:1: 30:2 help: run `rustc --explain E0309` to see a detailed explanation
…:27:1: 30:2 help: consider adding an explicit lifetime bound `R: 'a`...
…:27:1: 30:2 note: ...so that the reference type `&'a mut R` does not outlive the data it points at
…:27 struct PeekableRead<'a, R: Read> {
…:28 reader: &'a mut R,
…:29 peeked_octet: Option<u8>,
…:30 }
If I add the lifetime to R, as in, R: Read + 'a, it works. But why? Doesn't the 'a on the reference specify the lifetime? Must not reader: &'a mut R, in a struct PeekableRead<'a> live as long as the struct itself, and thus, "long enough"?
Oddly, I seem to need both; if I add 'a to R and remove it from the reference, I still get error: missing lifetime specifier. The only way I get a successful compilation is with both, but to me, they appear to specify the same thing redundantly.
(Also, why does rustc output the struct twice in the output? The second one looks like a suggestion of what to do, but appears to be exactly the same as what I have…)
Doesn't the 'a on the reference specify the lifetime?
It specifies the lifetime of the reference, but not the lifetime of the value being pointed to. Which explains your observation that
if I add 'a to R and remove it from the reference, I still get error: missing lifetime specifier.
For the struct to be valid we need both: the value being pointed to must still be alive, and so must the reference. (Although logically, the first condition is implied by the second, since a reference can never outlive the value it points to.).
The lifetime parameter on a reference designates the lifetime of the referent (i.e. the object the reference points to). That object may be a reference, or a complex object that contains one or more references. However, when you use a trait, the lifetime of the objects behind those references are somewhat hidden (the trait itself doesn't know about those lifetimes); lifetime bounds are what lets the compiler reason correctly about those lifetimes.
When you use a generic type behind a reference, you need to add a bound to ensure that instances of that type don't contain references that are shorter than the reference to that those instances. Bounds are not implicitly added just based on how you use types: the bounds should not change based on the details of what fields are in the struct and what type you define them to be.
For example, the type &'f File (for each 'f) implements Read. We can instantiate PeekableRead with that type: this gives us a PeekableRead<&'f File>. PeekableRead<&'f File> stores a mutable reference to a &'f File, so the concrete type of the reader field is &'a mut &'f File. In order for such a reference to be valid, 'a must be shorter or equal to 'f. If 'f was shorter than 'a, then you could replace the &'f File with one that would be dropped before the reference is dropped, leading to a dangling pointer. When you add the 'a bound to R (i.e. when you write R: Read + 'a), you say "instances of R must outlive 'a" (i.e. R may not contain references shorter than 'a).
Related
I was playing around with trees and traits and encountered a behavior I did not understand.
Here is a minimal (not compiling) example:
trait Trait<T> {}
struct Struct<T> {
option: Option<Box<dyn Trait<T>>>, // change this to Option<Box<TestStruct<T>>>
// and it works without issues
}
impl<T> Trait<T> for Struct<T> {}
fn set<T>(s: &mut Struct<T>) { // works when changed to "fn set <T: 'static> ..."
s.option = Some(Box::new(Struct { option: None })) // "error[E0310]: the parameter type `T` may not live long enough"
}
So this code works with either T: 'static or with Option<Box<TestStruct<T>>>, but not as is and I could not find a satisfying explanation why.
Can someone explain whats up with traits and lifetimes? Is there an other way to do this?
Whenever we use a trait object, here dyn Trait<T>, we're giving up a lot of information that Rust normally has about what characteristics a type has.
In particular, the values of a type might contain references, in which case that type has one or more lifetimes that specify how long those references are valid. (The simplest case is when the type is a reference itself; &'a str can't be around longer than the ending-time identified by 'a.
But a trait object doesn't specify a concrete type; it could be any type implementing the trait. This means that trait objects must always take into consideration what lifetimes the concrete type is allowed to contain. This is done by the “lifetime bound” of the trait object. Every trait object has a lifetime bound — it's just often unwritten thanks to lifetime elision.
In particular, the elision rules work out to saying that any time you write Box<dyn Trait>, that's equivalent to Box<dyn Trait + 'static>. That is, any type X that you want to turn into this trait object type must meet the bound X: 'static, which means that if it contains any lifetimes, they must outlive 'static — which in the specific case of the 'static lifetime, is the same as saying that they must be equal to 'static, because no lifetime is longer than 'static.
(The other common case for default trait object lifetime bounds is &'a dyn Trait, which the rules make equivalent to &'a (dyn Trait + 'a) — allowing anything that outlives or equals the lifetime of the reference.)
So, explaining what you observed:
Adding T: 'static works because your TestStruct<T> (which you did not give the definition of, so I'm guessing) contains no references, so T: 'static logically implies TestStruct<T>: 'static, which satisfies the trait object lifetime bound.
Using Option<Box<TestStruct<T>>> instead of Option<Box<dyn Trait<T>>> means that there is no trait object involved, so there is no default 'static bound, so Struct<T> is entirely generic — it doesn't care whether T has any lifetimes or not.
In most cases when you would want to use Box<dyn Trait> — to store it in some data structure — the T: 'static bound is the right choice, because things that you are going to store for later usually need to be not tied to a specific lifetime — able to live forever. But if you did need to admit types that have non-static lifetimes, you can always write Box<dyn Trait + 'a> instead (provided that the lifetime 'a is declared).
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.
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'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.
Given this code:
struct RefWrapper<'a, T> {
r: &'a T,
}
... the compiler complains:
error: the parameter type T may not live long enough
consider adding an explicit lifetime bound T: 'a so that the reference type &'a T does not outlive the data it points at.
I've seen this error multiple times already and so far I just listened to the compiler and everything worked out fine. However, thinking more about it, I don't understand why I have to write T: 'a.
As far as I understand, it is already impossible to get such a reference. Having &'a T implies that there is an object of type T that lives for at least 'a. But we can't store any references in said object which point to data having a shorter lifetime than 'a. This would already result in a compiler error.
In that sense it is already impossible to get a &'a T where T does not outlive 'a. Thus the additional annotation (T: 'a) shouldn't be necessary.
Am I right? Am I wrong and if yes: how could I break code, if T: 'a would not be required?
Links:
RFC introducing the syntax
Another maybe related RFC
This is part of the well-formedness rules. The type &'a T is only well-formed if T: 'a (“T outlives 'a”; it is required because we have a reference which we can access during the scope 'a; the pointed-to value in T needs to be valid for at least that scope, too).
struct RefWrapper<'a, T> is a generic type and it says you can input a lifetime 'x and a type U and get a RefWrapper<'x, U> type back. However, this type is not necessarily well-formed or even implemented unless the requirement T: 'a is respected.
This requirement comes from an implementation detail; it's not necessarily so that T and 'a are used together like &'a T in the struct's internals. The well formedness requirement needs to be promoted to the public interface of the RefWrapper struct, so that the requirements of forming a RefWrapper<'_, _> type are public, even if the internal implementation is not.
(There are other places where the same requirement T: 'a comes back but is implict:
pub fn foo<'a, T>(x: &'a T) { }
we spot a difference: here the type &'a T is part of the public api, too.)
Congratulations, you were right! As of Rust 1.31, thanks to RFC 2093, Infer T: 'x outlives requirements on structs, the requirement on the user to type out this restriction has been removed:
Remove the need for explicit T: 'x annotations on structs. We will infer their presence based on the fields of the struct. In short, if the struct contains a reference, directly or indirectly, to T with lifetime 'x, then we will infer that T: 'x is a requirement
Basically, there wasn't a case where this wasn't required, so there wasn't much value in forcing the programmer to write it out.