Compiling playground v0.0.1 (/playground)
error: lifetime may not live long enough
--> src/main.rs:11:9
|
9 | impl<'data, T: AsRef<[u8]> + Clone + 'data> S<'data, T> {
| ----- lifetime `'data` defined here
10 | fn as_str(&self) -> &'data CStr {
| - let's call the lifetime of this reference `'1`
11 | CStr::from_bytes_with_nul(self.data.as_ref()).unwrap()
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ associated function was supposed to return data with lifetime `'data` but it is returning data with lifetime `'1`
error: could not compile `playground` due to previous error
I tried (and failed) to say "The output reference lives as long as the field data, but may outlive self)".
Is there a way to assert this?
It's not possible to return a borrow of a value inside self that outlives self. The reference would be pointing to garbage data if self was dropped while the reference still existed.
The difference is that a generic parameter could OWN the data, while an explicit borrow doesn't. For example Vec<u8> implements AsRef<[u8]>, but if you drop S and the reference is still considered valid, the Vec's storage will have been deallocated.
You could create a trait that represents a borrow over a specific lifetime if you wanted a more abstract version of your concrete working example
use core::ffi::CStr;
// AsRef trait that incorporates a lifetime
trait AsRefLt<'a, T: ?Sized> {
fn as_ref_lt(&self) -> &'a T;
}
// Implement the trait for all references where the type implements AsRef
impl<'a, T: ?Sized, U: ?Sized> AsRefLt<'a, U> for &'a T
where
T: AsRef<U>,
{
fn as_ref_lt(&self) -> &'a U {
// Dereference self to get the reference with the correct lifetime
// (the compiler will do this automatically, I'm doing it explicitly for illustration purposes)
// Since AsRef ties the output lifetime to the input lifetime, we now have a reference with the lifetime we wanted
T::as_ref(*self)
}
}
struct S<T> {
data: T,
}
impl<'data, T: AsRefLt<'data, [u8]> + Clone> S<T> {
fn as_str(&self) -> &'data CStr {
CStr::from_bytes_with_nul(self.data.as_ref_lt()).unwrap()
}
}
fn main() {
let data = b"Hello, World!\0".to_vec();
println!("{:?}", S { data: &data }.as_str());
}
If we attempt to implement the new trait for a type which owns it's data we'll run into problems, which is a good sign since that wouldn't make any sense
impl<'a> AsRefLt<'a, [u8]> for Vec<u8> {
fn as_ref_lt(&self) -> &'a [u8] {
// No way to get from &self to &'a self here
&*self
}
}
error: lifetime may not live long enough
--> src\bin\main.rs:38:9
|
36 | impl<'a> AsRefLt<'a, [u8]> for Vec<u8> {
| -- lifetime `'a` defined here
37 | fn as_ref_lt(&self) -> &'a [u8] {
| - let's call the lifetime of this reference `'1`
38 | &*self
| ^^^^^^ associated function was supposed to return data with lifetime `'a` but it is returning data with lifetime `'1`
Note that T: 'data means that the type lives at least as long as 'data, but it doesn't change the lifetimes of any references methods on the type return.
let borrowed = {
// this works because `Vec<u8>: 'static`
let s: S<'static, Vec<u8>> = S { data: vec![b'a', b'b', b'c', b'\0'], _phantom: PhantomData };
// &'static CStr is valid forever
let cstr: &'static CStr = S::as_str(&s);
cstr
// `s` and the contained `Vec<u8>` are dropped here...
};
// ...but we can still access the data from the dropped `Vec<u8>` here -- uh oh
access(borrowed);
Remember: T: 'a only means that a value of type &'a T can soundly exist, not that it's sound to take &'a of any particular value of type T.
No. You name lifetimes to explain to the compiler what you know about how and when varibales are created, borrowed and destroyed.
They don't affect the execution of you program, they are part of built-in proof of correctness which compiler verifies before it run your program.
That one is easy: with concrete implementation compiler implicitly knows more about what is happening, with generics it only knows what you have told it.