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
Compare an iterator returned through impl Trait with one returned through
a boxed trait object. Repair the boxed iterator's lifetime and practice
returning a boxed predicate.
Implement these helper signatures while keeping the supplied run function:
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 groups in this order:
- Even values selected by
evens. - The same even values selected by
boxed_evens. - Values matching
picker: even whenwant_evenis true, odd otherwise.
Preserve order and duplicates within every group. Zero is even; negative
values follow the same parity rule. Empty input produces empty output.
For [1, 2, 3, 4], the result is [2, 4, 2, 4, 2, 4] when selecting evens
and [2, 4, 2, 4, 1, 3] when selecting odds.
The harness serializes the vector returned by run. The helpers demonstrate
return-type choices behind that entry point. Output tests alone cannot
distinguish all equivalent helper signatures; use the requested signatures
to practice the API design.
A hidden concrete type versus a trait object
Return-position impl Trait, often shortened to RPIT, hides the name of a
concrete return type. evens returns a particular iterator adapter type.
The signature itself does not require boxing or dynamic dispatch, but it
does not guarantee that arbitrary implementations allocate nothing or that
every call is inlined.
Box<dyn Iterator<...>> instead uses a trait object. In this return type,
omitting the object's lifetime bound defaults it to 'static. The iterator
borrows v, so add + '_ to tie that object bound to the input lifetime:
pub fn boxed_evens(v: &[i64]) -> Box<dyn Iterator<Item = &i64> + '_> {
Box::new(v.iter().filter(|n| *n % 2 == 0))
}
The lifetime of the referenced Item and the trait object's own lifetime
bound are separate parts of the type. The former does not automatically
replace the latter. Trait-object defaults also depend on context; do not
generalize this particular boxed return type to every dyn Trait use.
The starter's predicate already compiles
Each closure expression has its own type, but these two non-capturing closures can coerce to a common function-pointer type:
pub fn picker(want_even: bool) -> impl Fn(i64) -> bool {
if want_even {
|n: i64| n % 2 == 0
} else {
|n: i64| n % 2 != 0
}
}
This is valid Rust. For this exercise, change the return type to the
requested Box<dyn Fn(i64) -> bool> and box each branch. That demonstrates
type erasure; it is not the only way to express this operation.
A single closure capturing want_even is another possible design. In
general, RPIT requires one concrete return type, so distinct capturing
closure types may need restructuring or a shared wrapper.
Boxing does not always allocate: these particular closures capture nothing
and have zero-sized environments. Do not assume one heap allocation per
Box::new without considering the value's size. Trait-object calls permit
dynamic dispatch, though optimization may remove it in some contexts.
What edition 2024 changes
evens is valid in both editions 2021 and 2024. Its input lifetime already
appears in the returned iterator's Item = &i64 bound through elision.
It is not an example of the edition change.
For ordinary free functions without an explicit capture list, edition 2021 captures lifetimes mentioned in the RPIT bounds. Edition 2024 captures all in-scope generic lifetimes. Type and const parameters were already captured. Trait-method RPIT and async functions had broader capture rules before 2024.
Here is a complete example where the edition matters:
fn marker(_: &()) -> impl Sized {}
fn independent(x: &()) -> impl Sized + 'static {
marker(x)
}
It compiles in edition 2021. In edition 2024, marker's opaque return type
captures the input lifetime, so independent cannot promise a static result
for an arbitrary input borrow. This happens even though marker returns
unit and stores no reference. Capture affects the public opaque type's
contract, not just whether its current implementation contains a pointer.
Use a precise capture list when that dependency is unwanted:
fn marker(_: &()) -> impl Sized + use<> {}
fn independent(x: &()) -> impl Sized + 'static {
marker(x)
}
This version works in both editions. use<> captures no generic parameters.
A nonempty list names the parameters the opaque type may capture; it does
not extend any borrowed storage's lifetime. Precise capture lists have
restrictions, including listing all in-scope type and const parameters.
An outlives bound such as + 'a expresses a different promise from a
capture permission.
Keep evens as supplied, repair boxed_evens, and change picker to the
requested boxed API. Passing requires compilation, correct results and
Clippy with warnings treated as errors.
Your code, saved here
Sign in to access your accepted solutions and saved draft history. You can keep reading the lesson without an account.
Sign in to your accountStuck?
Rust reference solution
Sign in to track your attempts toward unlocking the reference solution.