Why does this "impl + 'static" code compile?

I noticed that the following code compiles even though it seems like it shouldn't. There is a method, add(), that requires a 'static parameter. The main function calls that method with a parameter that doesn't appear to have a static lifetime. What's going on?

use std::collections::HashMap;

trait App {}

struct AppRegistry {
    apps: HashMap<String, Box<dyn App>>,
}

impl AppRegistry {
    fn new() -> AppRegistry {
        Self { apps: HashMap::new() }
    }

    fn add(&mut self, name: &str, app: impl App + 'static) {
        self.apps.insert(name.to_string(), Box::new(app));
    }
}

struct MyApp {}

impl App for MyApp {}

fn main() {
    let mut registry = AppRegistry::new();
    registry.add("thing", MyApp {});
    println!("apps: {}", registry.apps.len());
}

According to the docs, there are only 2 ways to create objects with a static lifetime: make a constant with the static declaration, and Make a string literal which has type: &'static str. This is doing neither. See: https://doc.rust-lang.org/rust-by-example/scope/lifetime/static_lifetime.html

Thanks for any help!

The argument is passed by value, to be owned by the callee, and it doesn't contain any borrows that would limit its lifetime, so that makes it 'static. The callee could hold that value as long as it wants, including things like writing it to a global.

1 Like

Would it make sense for me to suggest a change to the Rust By Example book? The book says there are exactly two ways to make a static variable, but this seems to be a third way. There may be more ways still.

I would agree there's some nuance missing in that documentation.

Really, any type that lacks a generic lifetime parameter will satisfy a 'static constraint. Your MyApp is a trivial example, but also things like Option<Foo> and Vec<Bar> can be 'static. But once you borrow something, then you have a real limiting lifetime.

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

Now only MyStr<'static> would itself be 'static. Also, it used to be the case that you could omit writing such lifetimes when they can be inferred, like fn new(s: &str) -> MyStr, but now this is a warning and you should write MyStr<'_> for inference. This way we can always see where lifetimes are involved.

Back to those docs, I think the intent is to describe ways to get a 'static reference to such objects. From static NUM: i32, you can get a &'static i32 to pass elsewhere, and string literals are always &'static str. But you can not take a &'static reference to your app argument unless you store it somewhere that will live for the rest of the program. One way to do that is with Box::leak, leaving it on the heap forevermore.

5 Likes

A type T is 'static if any value of that type is guaranteed no to have any dangling references or borrows even if it were to exist / be owned until the end of time.

  • This definition may look contrived, but is exactly what is required for soundness: if you want to run a closure in another thread, which may run it arbitrarily late, Rust will require that the closure be guaranteed not to use any dangling pointers (that would be unsound). A way of guaranteeing it is by guaranteeing the stronger property of not even posessing such dangling pointers, and a way of guaranteeing that a particular instance does not posess such dangling pointer is by guaranteeing that all the elements / instances of that type cannot posess such dangling pointer.

As a practical rule of thumb, you can ask yourself the question: could I store a value of that type in a static / global variable?

For instance, a String, in practice, almost always ends up dropped before the end of the program, so you may be tempted to think that a String cannot be 'static. Which is false: imagine the following program:

use ::spin::{ // 0.5.2
    Mutex,
};

static GLOBAL_VARIABLE: Mutex<Option<String>> = Mutex::new(None);

fn main ()
{
    let s = String::from("Hello, World!");
    *GLOBAL_VARIABLE.lock() = Some(s);
    // Now `s` lives in / is held by a global variable,
    // until the end of the program life.
}

So, long story short, all the "primitive" types are 'static.

There are only two counter examples to this:

  • Rust references (&'a T or &'a mut T), which may dangle after the lifetime of the borrow ('a)

    • Only a &'static T / &'static mut T gets to be 'static (and requires that T : 'static)
  • any struct or enum / newtype containing a Rust reference gets "infected" with the 'a lifetime parameter and bound.

Example

use ::spin::{ // 0.5.2
    Mutex,
};

static GLOBAL_VARIABLE: Mutex<Option<&str>> = Mutex::new(None);

fn main ()
{
    let s = String::from("Hello, World!");
    *GLOBAL_VARIABLE.lock() = Some(&s);
    // Error, `&s` dangles at some point,
    // so it cannot be stored in a forever-living global variable
}
  • yields:

    error[E0597]: `s` does not live long enough
      --> src/main.rs:10:36
       |
    10 |     *GLOBAL_VARIABLE.lock() = Some(&s);
       |      ----------------------        ^^ borrowed value does not live long enough
       |      |
       |      argument requires that `s` is borrowed for `'static`
    ...
    13 | }
       | - `s` dropped here while still borrowed
    

Rule of thumb

  • Any non-generic type is automatically 'static

  • Any generic type is 'static only when instanced with 'static lifetimes (for the generic lifetime parameters) and types that recursively are 'static (for the generic type parameters).

    • For instance, the type Ref<'a, T>
      /// Two generic parameters:
      ///
      ///   - a lifetime parameter `'a`,
      /// 
      ///   - and a type parameter `T`.
      struct Ref<'a, T> {
          field: &'a T,
      }
      
      only gets to be 'static if T : 'static (e.g., T = String) and 'a = 'static
3 Likes

Thanks for the thorough treatment of this topic, @cuviper and @Yandros! My understanding now is that 'static is essentially an auto-trait that applies automatically to most types. 'static variables don't necessarily live forever; the 'static lifetime just means that the variable is capable of being used as a global.

I humbly suggest that you two should submit a PR for this page: https://doc.rust-lang.org/std/keyword.static.html :slight_smile:

2 Likes