Better error message for E0310

I'm suggesting rephrasing the error message for E0310 in order to make it more accessible. Consider the following contrived example:

use std::fmt::Display;

fn foo<T: Display>(value: T) -> Box<dyn Display> {
    let result: Box<dyn Display> = Box::new(value); // Error!
    result
}

Compilation of this code fails with the following error:

error[E0310]: the parameter type `T` may not live long enough
 --> src/lib.rs:4:36
  |
4 |     let result: Box<dyn Display> = Box::new(value);
  |                                    ^^^^^^^^^^^^^^^ ...so that the type `T` will meet its required lifetime bounds
  |
help: consider adding an explicit lifetime bound...
  |
3 | fn foo<T: Display + 'static>(value: T) -> Box<dyn Display> {
  |                   +++++++++

I'm fairly new to Rust. When I first encountered this errors, there were three things about this message I didn't understand:

Do types have a lifetime?

The error message states that "the parameter type T may not live long enough". In non-generic code, Rust's error messages talk about the lifetimes of concrete variables, not the lifetimes of types. My understanding is that there is that types themselves always exist and that there is no such thing as the lifetime of a type, only the lifetime of a variable of that type. So I guess "the parameter type T may not live long enough" is just shorthand for "variables of type T may not live long enough". The latter would have made the error message clearer to me.

How can value not live long enough?

The only variable of type T is value. This variable is passed by value, not by reference, and thus moved. I just couldn't think of a scenario where this variable might not live long enough.

The answer, as I now realize, is that T may be a struct containing references, which in turn may have a limited lifetime. Given that this is the only problematic case, it would have helped me if the error message had explicitly mentioned it.

Must I really limit my function to arguments with static lifetime?

The error message suggests adding the explicit lifetime bound 'static to T. When I first came across this message, I didn't realize that "static" as a trait bound has a different meaning to "static" as a reference lifetime. I assumed that adding the trait bound 'static to T would allow only arguments with a static lifetime to be passed, making the function useless. This is not the case. It would have helped me if the suggestion had mentioned this in some way.

Suggestion

I realize that much of my initial confusion comes from the fact that I'm rather new to Rust. But given that everybody starts as a newbie, I thought the error message could be extended a bit to make it easier to understand. Maybe something like this?

if the parameter type T contains references, variables of this type may not live long enough
...so that type T will meet its required lifetime bounds
help: consider excluding types with non-static references by adding an explicit lifetime bound...

13 Likes

Please open an issue on the GitHub issue tracker — there's a category specifically for suboptimal error messages like this.

My suggestion would to be to change the first line to

lifetimes in the parameter type T may not live long enough

to hint at the subtlety here.

There's also more context available in --explain E0310, but unfortunately that's rarely actually looked at.

6 Likes

Side note: I've been using Rust for awhile now and I've never understood this error message until this thread. I don't have any suggestions on the verbiage but just wanted to say thank you for working on this error message.

17 Likes

Please open an issue on the GitHub issue tracker — there's a category specifically for suboptimal error messages like this.

Thanks for the suggestion; I wasn't aware of this category. I've created an issue for improving this error message.

3 Likes

What's also confusing about this error message is that it doesn't mention the implicit 'static lifetime in the returned dyn Debug as the source of the 'static bound requirement in the first place. The 'static bound is not necessary if one adds a lifetime to the returned trait object:

use std::fmt::Display;

fn foo<'a, T: Display + 'a>(value: T) -> Box<dyn Display + 'a> {
    let result: Box<dyn Display> = Box::new(value);
    result
}

... which compiles and could be a potential second suggestion the compiler gives.

The tendency for the compiler to default to suggesting placing 'static bounds on arguments is mentioned in quite a few outstanding bugs, and particularly relevant seems this one

11 Likes

I'll try to contribute to the issue later, but here are some notes that came to mind when reading your OP.


I suggest you don't use the term "lifetime" when talking about how long a variable sticks around, like here:

I assumed that adding the trait bound 'static to T would allow only arguments with a static lifetime to be passed, making the function useless.

I prefer phrasing more like "liveness scope" or "when the value is dropped", as it helps avoid confusion with lifetime bounds / requirements on types.

I also think of bounds like T: 'lifetime as "is valid for".

I do agree "live long enough" is suboptimal phrasing that implies there's a problem with the liveness scope of the variable, as opposed to a lifetime bound on the type (its validity constraint).


Types can have lifetimes (lifetime parameters).

  • For example, &'a str and &'b str are distinct types unless 'a == 'b
  • There is no higher-ranked for<'any> &'any str type that represents a &str with any lifetime

And this can be relevant

  • When things are only implemented for some 'static lifetime parameter, or for lifetime parameters that have a relationship between each other ('a: 'b)
  • Anywhere invariance comes into play, more generally (like in trait parameters)
  • In generic contexts where you need something higher-ranked
    • For example, the following can never take a for<'x> fn(&'x str) -> &'x str because T must resolve down to a single type -- it can only support a single lifetime, not "any lifetime"
      fn foo<T, F: Fn(T) -> T>(f: F) {}
      

Or phrased differently, a type with a lifetime parameter is more properly called a type constructor; a concrete lifetime must take the place of the parameter in order to form a concrete type. [1] It's reasonable to say these types "have a lifetime".

Trait objects also always have a (single) lifetime parameter, though it can often be elided (though in turn the elision can cause confusion and poor diagnostics, as already mentioned).


Types don't have to contain references to end up constrained to a non-'static lifetime bound. It's common and useful as an example, but it's not the only way. (And types containing references can be 'static, if the references are.)


  1. There are also some higher-ranked types, but those don't take a lifetime parameter per se. ↩ī¸Ž

3 Likes

Not entirely true, they exist, and are known as unbounded lifetimes. They are primarily, but not necessarily, created via unsafe code, and they will freely coerce into any other valid lifetime. In practice, they mostly just behave like a 'static lifetime.

There is no for<'any> &'any _ type. The example in that link is a function item type. It can't produce a for<'any> &'any str either, as there is no such thing. It can produce a &'x str for any concrete lifetime 'x though.

I've got to say I think this is a great idea. When I first came across these error messages I had exactly the same experience. It was only in the discord server when this was explained to me. I believe the wording proposed is much clearer.

Almost every suggestion to add 'static bound from rustc is wrong, so I'd be happy to see improvements here.

1 Like