We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Trait Objects and Dispatch step 8 of 10
Trait objects and lifetimes: Box<dyn Trait + 'a>
A factory function that returns a boxed closure is one of the most common shapes in real Rust — configuration builders, parser combinators, callback registries, middleware stacks. It is also the place where the default object lifetime rule bites hardest, and the fix is invisible unless you know the rule.
Write this and it will not compile:
pub fn make_tagger(prefix: &str) -> Box<dyn Fn(&str) -> String> {
Box::new(move |s| format!("{prefix}:{s}"))
}
Why, precisely
Box<dyn Trait> is shorthand. Every trait object carries a lifetime bound
saying how long the erased value may live, and when you leave it out the
compiler picks a default. Inside a Box in return position that default is
'static. So you actually wrote:
-> Box<dyn Fn(&str) -> String + 'static>
You then put a closure in it that captured prefix: &str — a borrow with some
caller-chosen lifetime that is emphatically not 'static. Contradiction.
The diagnostic is lifetime may not live long enough, and it has no error
code. rustc --explain cannot help you; there is nothing to explain. This is
one of the genuinely undiagnosable-by-lookup errors in Rust, and knowing to
read the message body rather than reach for --explain is part of the skill.
The fix is one token:
pub fn make_tagger(prefix: &str) -> Box<dyn Fn(&str) -> String + '_> { ... }
+ '_ means “tie this trait object to whatever lifetime elision inferred for
the inputs” — here, prefix. The returned closure may not outlive the string
it borrowed. That is exactly true, and now the compiler knows it.
The two lifetimes, which are unrelated
This is the part worth slowing down for. There are two lifetimes in
Box<dyn Fn(&str) -> String + '_> and they have nothing to do with each other.
-
'_— how long the closure itself may live, bounded by the borrow ofprefixit captured. -
the elided lifetime inside
Fn(&str)— how long the argument passed to each call must live. That one is universally quantified: the closure accepts a&strof any lifetime, on every call, independently.
Beginners routinely try to unify them and end up writing
Box<dyn Fn(&'a str) -> String + 'a>, which compiles but means something much
more restrictive: “only ever callable with strings that live as long as the
prefix”. This is the first taste of higher-ranked trait bounds — the for<'a>
you can see in the MakeTagger type alias in the starter, which says exactly
“for every lifetime 'a“.
move is also required. Without it the closure borrows prefix from the
function’s own stack frame, which is gone the instant you return.
What to write
The two factories and the entry point:
pub fn make_tagger(prefix: &str) -> Box<dyn Fn(&str) -> String + '_>
pub fn make_static_tagger(prefix: String) -> Box<dyn Fn(&str) -> String>
pub fn run(prefix: String, inputs: Vec<String>) -> (Vec<String>, Vec<String>)
Both taggers produce format!("{prefix}:{s}"). make_static_tagger takes the
prefix by value — it owns a String, borrows nothing, and so the 'static
default is honest and needs no annotation. That contrast is the whole point:
the difference between the two signatures is not style, it is who owns the
prefix.
run applies the borrowing tagger to every input to build the first vector,
and the owning tagger to build the second. The two vectors are identical in
content — the exercise is entirely about making both spellings compile.
The const _: pins at the top of the file assert the signatures. Leave them
in. If you change a signature they will stop compiling, which is the point.
Errors you may meet
-
lifetime may not live long enough(no code) — the default'static. - E0597 — a borrowed value does not live long enough; you kept the closure past the prefix.
-
E0716 — a temporary is dropped while still borrowed.
make_tagger(&format!("x"))on one line does this. - E0621 — an explicit lifetime bound is needed; usually you named one lifetime where you needed two.
Remember the grade is compile + tests + clippy -D warnings.
Loading visualization…
Stuck?
Rust reference solution
Sign in to attempt this problem and reveal the reference solution.