What does it mean when we let a trait inherits 'static?
Asked Answered
B

3

7

Rust supports trait inheritance, as follows:

pub trait A {}
pub trait B: A {}

B: A means that if some type T implements B, it also needs to implement all the methods in A.

But today I see the following code:

trait Display: 'static {
    fn print(&self);
}

What does it mean? It doesn't seem to be trait inheritance.

Bucky answered 10/11, 2021 at 8:59 Comment(1)
"Inheritance" might be the wrong mental model here. trait B: A just means "if you implement B, you must also implement A". For the user of the trait, this has similar effect to that of inheritance because they can require B and count on getting A as well. But an implementor of B experiences no inheritance, they have to also (and separately) implement A. In that light, Display: 'static means that, as a user of Display, you can count that the type that implements it has a 'static lifetime bound, which means that it doesn't borrow non-static data.Temper
B
5

Rust doesn't have inheritance.

What it has is a way to define constraints. For example a trait may be constrained to only be implemented by types which implement another trait.

In your case the constraint is a lifetime bound.

To implement your Display trait, an object may contain references but in this case their lifetime must respect this constraint.

Let's suppose you have this type:

struct S<'a> {
    s: &'a str,
}

Then you can't implement the trait for any lifetime, but only 'static.

impl Display for S<'static> {
    fn print(&self){}
}

fn main() {
    let s1 = "test";
    let a = S { s: s1 };
    a.print(); // compiles

    let s2 = "test".to_string();
    let a = S { s: &s2 };
    a.print(); // doesn't compile because s doesn't live long enough
}

Brandes answered 10/11, 2021 at 9:13 Comment(0)
F
6

Rust supports trait inheritance, as follows [...] B: A means that if some type T implements B, it also needs to implement all the methods in A.

Technically, that is not inheritance but requirement. It is a trait bound not entirely dissimilar to one you'd have in a function: it constraints the type on which B is implementable to only types on which A is already implemented.

With that change in wording, the second version is much easier to understand: it's a lifetime bound, meaning it constraints the type on which B is implementable to only types with 'static lifetime, meaning if you're trying to implement B on a type, that must either have no lifetime at all, or have a 'static lifetime (or the implementation must have a lifetime bound aka only work for some uses of the type).

You can see that if you try to implement the trait on a lifetime-generic structure:

struct A<'a>(&'a str);
trait Display: 'static {
    fn print(&self);
}

impl <'a>Display for A<'a> {
    fn print(&self) { todo!() }
}

will yield

error[E0478]: lifetime bound not satisfied

That is because 'a can be anything, so implementing Display for A<'a> means it is also implemented for non-'static instances, which is not valid.

By adding the relevant lifetime bound on the impl, and thus limiting the implementation to A<'static> instances:

struct A<'a>(&'a str);
trait Display: 'static {
    fn print(&self);
}

impl <'a: 'static>Display for A<'a> {
    fn print(&self) { todo!() }
}

the requirements of the trait are satisfied, and the impl is valid (nb: the 'a is not necessary here you can just impl ... for A<'static>, I'm showing it for regularity).

And if your struct has no lifetime it works by default, because no lifetime ~ 'static:

struct A(String);
trait Display: 'static {
    fn print(&self);
}

impl Display for A {
    fn print(&self) { todo!() }
}
Fermentative answered 10/11, 2021 at 9:20 Comment(0)
B
5

Rust doesn't have inheritance.

What it has is a way to define constraints. For example a trait may be constrained to only be implemented by types which implement another trait.

In your case the constraint is a lifetime bound.

To implement your Display trait, an object may contain references but in this case their lifetime must respect this constraint.

Let's suppose you have this type:

struct S<'a> {
    s: &'a str,
}

Then you can't implement the trait for any lifetime, but only 'static.

impl Display for S<'static> {
    fn print(&self){}
}

fn main() {
    let s1 = "test";
    let a = S { s: s1 };
    a.print(); // compiles

    let s2 = "test".to_string();
    let a = S { s: &s2 };
    a.print(); // doesn't compile because s doesn't live long enough
}

Brandes answered 10/11, 2021 at 9:13 Comment(0)
T
0

While other answers have already mentioned that this is a Lifetime Bound, that term can be a bit confusing so let me try to add a different explanation in the hopes that it might help some people.


Lifetime Bounds are effectively a special kind of (auto) trait.

The 'a 'trait' / lifetime bound is satisfied by all types that contain no references that might live shorter than 'a.

That's all there is to it.

'static is then just a special case that is satisfied by all types that contain no references that will ever become invalid.


Here's an example of why this concept is useful/necessary:

// Basic definitions for demo purposes
trait Foo {
    fn get_value(&self) -> &i32;
}
struct Bar<'a>{
    value: &'a i32
}
impl<'a> Foo for Bar<'a>{
    fn get_value(&self) -> &i32 {
        self.value
    }
}

// Now, this seemingly innocent method already cannot be allowed by the compiler.
// As for why, see below in `main`
fn push_foo(tgt: &mut Vec<Box<dyn Foo>>, foo: impl Foo) {
    tgt.push(Box::new(foo));
}

// demonstration why `push_foo` must be illegal:
fn main() {
    let mut list_of_foos = Vec::new();
    {
        let value = 42;
        let bar = Bar { value: &value };
        push_foo(&mut list_of_foos, bar);
    }
    // `value` went out of scope at this point, so this would be a use after free bug
    // therefore, the `push_foo` function cannot be allowed to compile
    let _access_value_after_scope_ended = *list_of_foos[0].get_value();
}

// The simple fix for this issue is to force `foo` to not contain any 
// data that has *any* additional lifetime bounds (== `foo` shall satisfy the 'static Lifetime Bound).
// (The `Box<dyn Foo>` in this signature is a shorthand for `Box<dyn Foo + 'static>`)
fn push_foo_fixed_v1(tgt: &mut Vec<Box<dyn Foo>>, foo: impl Foo + 'static) {
    tgt.push(Box::new(foo));
}

// To fix the problem more generally, we could express the following additional guarantee:
// There exists a lifetime `'a` that is at least as long as any Lifetime Bound on
// `foo` or the `dyn Foo`s in `tgt`:
fn push_foo_fixed_v2<'a>(tgt: &mut Vec<Box<dyn Foo + 'a>>, foo: impl Foo + 'a) {
    tgt.push(Box::new(foo));
}

[ Rust Playground ]

Tekla answered 9/11 at 12:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.