We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Trait Objects and Dispatch step 1 of 10
Box<dyn Trait>: trait objects, DSTs and dynamic dispatch
Everything you have written with traits so far has been static dispatch: a
generic fn draw<S: Shape>(s: &S) gets one compiled copy per concrete S,
and the call is resolved before your program runs. That is fast, and it is
useless the moment you want a heterogeneous collection — a list holding a
circle and a rectangle and a thing a plugin registered five minutes ago.
Vec<S> cannot do that: every element of a Vec<S> is the same S. This is
the problem dyn Trait exists to solve, and it is the Rust answer to Java’s
List<Shape>.
Why the Box is mandatory, not decoration
dyn Op is a dynamically sized type (a DST). It means “some value,
somewhere, that implements Op“ — and different implementors have different
sizes, so the compiler cannot tell you size_of::<dyn Op>(). There is no
answer.
Every generic parameter in Rust carries an implicit Sized bound. When
you write
struct Vec<T> { ... }
you are really writing struct Vec<T: Sized>. Rust adds it for you, because
99% of the time you want it, and the escape hatch is the odd-looking
T: ?Sized (“T may or may not be sized”). Vec<dyn Op> therefore fails
with E0277: the size for values of type dyn Op cannot be known at
compilation time.
Put it behind a pointer and the problem evaporates, because pointers have a
known size. Box<dyn Op> is a fat pointer: two machine words, one
pointing at the data and one pointing at a vtable — a small static table
of function pointers for that concrete type’s impl Op. Calling
op.apply(x) loads the function pointer out of the vtable and calls it. That
indirection is what “dynamic dispatch” means.
The lint you must un-learn
Earlier tracks taught you to fear Vec<Box<T>> — clippy’s vec_box lint
fires on Vec<Box<String>> and tells you the Box is a pointless extra
allocation and indirection. vec_box does not fire on
Vec<Box<dyn Trait>>, and it is right not to: it only fires when T is
Sized, which is exactly the case where the Box was redundant. Here the
Box is load-bearing. If a reviewer tells you to remove it, they are wrong.
What to write
A trait and three implementors are given. You write three things.
pub fn build(spec: &[(String, i64)]) -> Vec<Box<dyn Op>>
Turn a specification into a heterogeneous vector of operations. The names are
"add", "mul" and "clamp" (which does x.min(n)). Any other name is
skipped entirely — it contributes no operation.
pub fn compose(fns: Vec<Box<dyn Fn(i64) -> i64>>) -> Box<dyn Fn(i64) -> i64>
Fold a list of boxed closures into a single boxed closure applying them
left to right: compose([f, g])(x) == g(f(x)). An empty list composes to
the identity function. Box<dyn Fn(..) -> ..> is how you store a closure in
a struct field or return one from a function, and it is by far the most
common trait object in real code — every closure has its own unnameable
anonymous type, so a trait object is the only way to put two of them in one
Vec.
pub fn pipeline(ops: Vec<(String, i64)>, start: i64) -> i64
Build the operations and fold start through them in order.
Errors you may meet, and what they mean
-
E0277 —
the size for values of typedyn Opcannot be known at compilation time. You wrote a DST where aSizedtype was required. -
E0782 —
expected a type, found a trait. WritingBox<Op>instead ofBox<dyn Op>was merely a warning in edition 2015; since edition 2021 it is a hard error. Thedynkeyword is not optional. -
E0746 —
return type cannot have an unboxed trait object. You wrote-> dyn Op. ReturnBox<dyn Op>orimpl Op. - E0038 — the trait is not dyn compatible. The next item is entirely about that one.
Remember the grade is compile + tests + clippy -D warnings.
Stuck?
Rust reference solution
Sign in to attempt this problem and reveal the reference solution.