Box<dyn Error>
There is a lifetime in that type. You did not write it, nothing on the page
hints at it, and it is 'static.
This is the most surprising set of implicit rules in Rust, and you meet them the
first time you box a closure or return a boxed error. The errors it produces
mention 'static and point at code that contains no lifetimes at all.
The default rules
A dyn Trait always has a lifetime bound. When you do not write one, the
Reference’s object lifetime defaults supply it, in this order:
-
If the trait itself declares a lifetime bound, that is used. For
trait Foo: 'a, adyn Foogets'a. - Otherwise the containing type decides. Specifically: if the type that contains the trait object imposes exactly one lifetime bound on its parameter, that bound is used.
-
If more than one bound applies and one of them is
'static,'staticwins. -
If nothing applies, the default is
'staticin type positions.
In practice, two cases account for almost everything you will meet:
Box<dyn Foo> // means Box<dyn Foo + 'static>
&'a dyn Foo // means &'a (dyn Foo + 'a)
Why the difference? Box<T> is declared with no lifetime bound on T — it
owns its contents and has no opinion about how long they live — so rule 4 fires
and you get 'static. &'a T structurally requires T: 'a, so rule 2 fires
and the object inherits 'a.
And you can always opt out and let inference do it:
Box<dyn Foo + '_>
Where it bites
Returning a boxed thing built from a reference:
use std::fmt::Debug;
fn wrap(s: &String) -> Box<dyn Debug> {
Box::new(s)
}
error: lifetime may not live long enough
returning this value requires that `'1` must outlive `'static`
Note the shape of that message. It says 'static. Your function contains no
'static, no 'a, no lifetime of any kind. The 'static came from the default
rule, and the fix is to opt out of it:
fn wrap(s: &String) -> Box<dyn Debug + '_> {
Box::new(s)
}
A struct field holding boxed closures:
struct Registry { handlers: Vec<Box<dyn Fn(i64) -> i64>> }
That field type means + 'static and produces no warning at all. Push a
closure that captures a reference into it and the error lands somewhere else
entirely:
let t = vec![1i64];
let tr: &Vec<i64> = &t;
let mut r = Registry { handlers: Vec::new() };
r.handlers.push(Box::new(move |x| x + tr[0]));
// error[E0597]: `t` does not live long enough
The complaint is about t, a Vec that is alive for the whole function. The
actual cause is a 'static bound three lines away in a struct declaration that
contains no lifetimes. That invisibility is the lesson. The fix is to give
the struct a lifetime parameter and spell the bound out:
struct Registry<'a> { handlers: Vec<Box<dyn Fn(i64) -> i64 + 'a>> }
💡Why does &'a Box<dyn Foo> still contain a 'static object, when &'a dyn Foo does not?
click to reveal
Because the innermost containing type sets the bound, and here that is the Box, not the reference.
Work outward. The trait object sits inside Box<...>. Box<T> imposes no lifetime bound on T, so the default rule fires and the object becomes dyn Foo + 'static. Only then is the whole Box<dyn Foo + 'static> placed behind a &'a. The outer reference’s 'a never gets a chance to influence the object — the question was already settled one level in.
So &'a Box<dyn Foo> means &'a Box<dyn Foo + 'static>, while &'a dyn Foo means &'a (dyn Foo + 'a). Adding a Box in the middle changes the meaning of the innermost part of the type, which is genuinely counterintuitive and catches experienced people.
A side note you will hit in review: &Box<T> as a parameter type also trips clippy::borrowed_box — “you seem to be trying to use &Box<T>. Consider using just &T“ — which is warn-by-default and fatal here. Taking &dyn Foo instead both silences the lint and gives you the more useful default.
The one place the default does not apply
Inside a function body, a let annotation can have its object lifetime
inferred rather than defaulted:
let s = String::from("hi");
let b: Box<dyn Debug> = Box::new(&s); // compiles
This surprises people who have just learned the rule and expect 'static. The
defaults are about type positions in signatures and declarations — function
signatures, struct fields, type aliases — where there is nobody to infer from.
In an expression, there is.
Do not build a mental model on the let case. Test your understanding against
signatures and struct fields, which is where the rule actually governs and where
every real bug lives.
The error code that no longer exists
If you search for the boxed-closure error you will land on E0759, whose
explanation page is genuinely the best writing on this topic anywhere. It
explains exactly why fn foo(x: &i32) -> Box<dyn Debug> fails and why
Box<dyn Debug + '_> is the fix.
It is also dead:
$ rustc --explain E0759
#### Note: this error code is no longer emitted by the compiler.
Read the page — it is worth it — but expect the bare
error: lifetime may not live long enough in your terminal, and do not waste
time looking for the code in your output.
💡Box<dyn Error> and Box<dyn Error + Send + Sync + 'static> both appear constantly in real code. What is the difference, and why is one of them so much longer than it needs to be?
click to reveal
In terms of the object lifetime, there is no difference: Box<dyn Error> already means Box<dyn Error + 'static>, so the explicit 'static in the second is redundant. It is written out for emphasis, and because the surrounding + Send + Sync makes the whole bound list feel like something you should spell out.
The real difference is Send + Sync. Box<dyn Error> can hold an error that is not safe to move between threads or share across them. Box<dyn Error + Send + Sync> can be returned from a spawned thread, stored in a shared error channel, or held by a 'static future — which is why every async runtime and most library APIs use the longer form.
The practical guidance: use Box<dyn Error + Send + Sync + 'static> in library code that might be used from threaded contexts, and Box<dyn Error> in an application’s main where nothing crosses a thread boundary. The 'static is optional noise either way — but written down, it does at least remind the reader that a boxed error cannot borrow from anything.
The card
| you write | it means | why |
|---|---|---|
Box<dyn Foo> |
Box<dyn Foo + 'static> |
Box<T> bounds nothing |
&'a dyn Foo |
&'a (dyn Foo + 'a) |
&'a T requires T: 'a |
&'a Box<dyn Foo> |
&'a Box<dyn Foo + 'static> |
innermost container wins |
Box<dyn Foo + '_> |
inferred | explicit opt-out |
And the habit: when an error mentions 'static and your code contains no
'static, look for a dyn.