We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Generics and Traits step 18 of 24
Returning impl Trait, and what edition 2024 changed
-> impl Trait in return position (RPIT) is how you hand back a closure or an
iterator chain without boxing it. The type is real and concrete — the compiler
knows exactly which Filter<Iter<'_, i64>, {closure}> you built — but the
caller only sees the promise. No allocation, no vtable, full inlining.
It is also where the most confusing edition difference in Rust lives, and where a large fraction of the tutorials you will find online are now wrong.
Your task
Three functions, one of them already correct:
pub fn evens(v: &[i64]) -> impl Iterator<Item = &i64>
pub fn boxed_evens(v: &[i64]) -> Box<dyn Iterator<Item = &i64> + '_>
pub fn picker(want_even: bool) -> Box<dyn Fn(i64) -> bool>
pub fn run(nums: Vec<i64>, want_even: bool) -> Vec<i64>
run concatenates three lists: the evens via evens, the evens again via
boxed_evens, and then every element matching the closure picker returned.
For [1,2,3,4] with want_even = true that is [2,4, 2,4, 2,4]; with
want_even = false it is [2,4, 2,4, 1,3].
run is written for you. Fix the two broken signatures.
(Note the harness constraint: the graded function cannot itself return
impl Iterator, because main has to serialise the result to JSON. The RPIT
functions are internal helpers behind a Vec-returning entry point — which is
how you would structure real code anyway.)
Broken signature 1: the asymmetry
This compiles:
pub fn evens(v: &[i64]) -> impl Iterator<Item = &i64>
This does not:
pub fn boxed_evens(v: &[i64]) -> Box<dyn Iterator<Item = &i64>>
error: lifetime may not live long enough
|
| returning this value requires that `'1` must outlive `'static`
help: to declare that the trait object captures data from argument `v`,
you can add an explicit `'_` lifetime bound
| pub fn boxed_evens(v: &[i64]) -> Box<dyn Iterator<Item = &i64> + '_> {
| ++++
The asymmetry is the entire teaching point. These are two different defaulting rules that happen to sit next to each other:
-
An RPIT captures every in-scope generic parameter, lifetimes included,
automatically.
evensneeds nothing. -
A trait object in a
Boxdefaults to+ 'staticunless you write a lifetime.boxed_evensmust say+ '_.
Learners over-generalise in both directions — either sprinkling '_ onto
RPITs where it is now redundant, or omitting it from Box<dyn ...> where it
is still required. They are separate rules. Memorise them separately.
Broken signature 2: one return type, not two
pub fn picker(want_even: bool) -> impl Fn(i64) -> bool {
if want_even { |n: i64| n % 2 == 0 } else { |n: i64| n % 2 != 0 }
}
error[E0308]: `if` and `else` have incompatible types
= note: no two closures, even if identical, have the same type
impl Trait is not a trait object. It means “one specific type that I am
not naming”, and two closure literals are two different types no matter how
alike they look. The fix is to erase them to a common type:
Box<dyn Fn(i64) -> bool>, with each branch boxed. You pay one allocation and
one indirect call; in exchange the signature becomes expressible.
The neighbouring error worth recognising: writing -> dyn Fn(i64) -> bool
with no Box gives E0746, “return type cannot have an unboxed trait
object”. dyn Trait is unsized, and Rust returns values on the stack.
The edition 2024 capture change
This is the part that makes old tutorials wrong.
In edition 2021, an RPIT captured a lifetime only if that lifetime
appeared syntactically in the impl Trait bound. So
fn f(_: &()) -> impl Sized {}
meant “captures nothing” — in today’s notation, + use<>. That is why 2021
code is full of + '_ annotations on iterator-returning functions: without
them the compiler refused to let the returned value borrow from the argument.
In edition 2024 all in-scope generic parameters, including lifetimes,
are captured unconditionally. The same signature now means + use<'_>. This
is why evens above needs no annotation, and why a tutorial telling you to
add + '_ is describing a language that no longer exists.
You can see the change directly. This compiled in 2021 and is an error in 2024:
fn test<'a>(x: &'a ()) -> impl Sized + 'static { capture(x) }
In 2021 'a was not captured, so the result really could be 'static. In
2024 'a is captured, so it cannot.
Since Rust 1.82 there is explicit control, use<..>:
fn g<'a, T>(x: &'a T) -> impl Sized + use<'a, T> { ... } // capture these
fn h(x: &()) -> impl Sized + use<> { } // capture nothing
Two lints exist for this, impl_trait_overcaptures and
impl_trait_redundant_captures, and both are rustc allow-by-default —
they will not fire unless you turn them on. If you want to see them, put
#![warn(impl_trait_overcaptures)] at the very top of your submission; your
code is the first thing in the compilation unit, so a crate-level attribute
there is legal.
One thing not to over-generalise: type and const parameters were always captured, in every edition. Only lifetimes changed. People read the changelog and conclude the whole capture story is new; it is not.
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.