'static lifetime with owned object?

Hi,

Is lifetime annotation 'static only for references, or also for owned objects? I don't understand what is the purpose of 'static when used as lifetime bound for owned object, for example, in this case:

pub fn send(&mut self, reader: impl Read + 'static) -> Response

(This API is part of this crate doc)

The distinction between owned and non-owned "objects" becomes a bit more complicated when you consider types like Vec<&u8>. The vector is owned but it contains references which borrow some other data. Similar to this are e.g. structs that contain references. The bound T: 'static basically means that T is a type that does not contain any non-'static references, even nested. (Static references like e.g. &'static str are allowed.)

One of the most popular cases of a T: 'static bound is std::thread::spawn. The reason there is that the closure and its return value need to be sent between threads, escaping their call-stack which is why they cannot contain any non-'static references since these could become invalidated in the other thread in the mean-time.

5 Likes

This is a good detailed explanation. A more pithy explanation is:
If a type is : 'static, it's possible for it to live for the rest of the program's duration.

This includes owned objects. A Vec<f64> can live for the rest of the program's duration. If you own that vec, you know it will live for as long as you do, up until the end of the program.
Since a static variable will live for the rest of the program's duration no matter what, it fits the above statement.

I have the following blog post about common rust lifetime misconceptions bookmarked. I've linked here the section about 'static. If you have the time, the rest of it is a great read too.

1 Like

Note that the signature does not imply that reader is an owned object, with or without the 'static bound. It looks a bit silly in this case, but traits can be implemented for borrows as well:

use std::io::Read;

fn main() {
    let mut s = S {};
    let b = BorrowReader {};
    s.send(&b);
}

struct BorrowReader {}

impl Read for &BorrowReader {
    fn read(&mut self, _buf: &mut [u8]) -> std::io::Result<usize> {
        todo!()
    }
}

struct S {}

impl S {
    pub fn send(&mut self, reader: impl Read) -> () {
        todo!()
    }
}

(This can be done even with the 'static bound, but the example is simpler without it)

1 Like

Lifetimes don't apply to types that don't borrow anything. This makes self-contained types appear to satisfy any lifetime, but in fact they ignore all lifetimes.

T: 'static will allow a type like String. It's not because String lives for as long as 'static (it could be dropped at any time), but because it's not borrowing from any scope, so there's no lifetime limiting its usage.

In practice + 'static just means that temporary references are forbidden. It doesn't want things with a static lifetime. It wants to exclude temporary references, and these happen to always have less-than-static lifetime.

1 Like

In this particular case, there is no fundamental reason for why the restriction is necessary except for some library-internal types that aren’t generic over lifetimes and contain a Box<dyn Read + 'static> that gets populated with the argument to send. I’ve tried modifying the library and it compiles just fine with some extra internal lifetime-generics, here’s a PR with the changes.

2 Likes

Thank you for the PR!