We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Trait Objects and Dispatch step 4 of 10
Delegate shape methods through an enum
Complete make, the Shape implementation for AnyShape, and total_area. Keep the supplied trait, concrete shapes and enum variants. Store each concrete shape directly in its variant, using safe Rust without allocating in make or the delegated methods.
pub fn make(kind: u8, a: f64, b: f64) -> AnyShape
pub fn total_area(shapes: &[AnyShape]) -> f64
Construct and delegate
Select a variant using kind % 3. Preserve the supplied dimensions exactly:
| Remainder | Stored shape | area() calculation | name() |
|---|---|---|---|
| 0 | Circle { r: a } | (PI * a) * a |
"circle" |
| 1 | Rect { w: a, h: b } | a * b |
"rect" |
| 2 | Triangle { b: a, h: b } | (0.5 * a) * b |
"triangle" |
PI is std::f64::consts::PI. Circle ignores b. Do not take absolute values, reject negative dimensions or change the formulas; delegate to the supplied concrete implementations. Every u8 kind is valid, with the selection repeating every three values. Shape names are lowercase static strings.
total_area starts at positive 0.0 and adds each area in slice order, from left to right. Empty input returns positive zero. Keep f64 operations and this addition order: use an explicit loop or fold, rather than relying on an unspecified reduction order. Preserve repeated shapes and the input slice.
The starter has unfinished methods and an area match missing Triangle. Complete every method and cover all three variants explicitly.
What enum dispatch means
AnyShape names the concrete possibilities in one type. Its Shape methods inspect the variant and call the corresponding concrete method. This complete smaller example demonstrates delegation:
trait Named { fn name(&self) -> &'static str; }
struct Circle;
struct Rect;
impl Named for Circle { fn name(&self) -> &'static str { "circle" } }
impl Named for Rect { fn name(&self) -> &'static str { "rect" } }
enum Item { Circle(Circle), Rect(Rect) }
impl Named for Item {
fn name(&self) -> &'static str {
match self {
Item::Circle(value) => value.name(),
Item::Rect(value) => value.name(),
}
}
}
fn main() {
let items = [Item::Circle(Circle), Item::Rect(Rect)];
assert_eq!(items[0].name(), "circle");
assert_eq!(items[1].name(), "rect");
}
A Vec of these enums stores its elements contiguously. The Vec has its own storage allocation, but the supplied shape payloads need no separate Box per element. The compiler can see the concrete calls in each arm; whether it inlines them or uses branches or a jump table depends on optimization. This is an opportunity for efficient code, not a promised speedup over dyn.
Understand the tradeoffs
An enum fixes its variants at its definition. Adding one requires updating exhaustive matches such as the ones here. A match with a wildcard may still compile, so the compiler does not necessarily identify every behavior you should reconsider.
A trait object provides a different interface for runtime-selected implementations. Generics can also support user-defined implementations. Choose based on storage, extensibility and caller needs, rather than assuming a small enum is always preferable.
Each enum value needs room and alignment for its possible payloads. Its exact layout is not simply the largest payload plus a fixed tag: padding and representation choices matter. A much larger variant may warrant a different design in other code, but keep the inline variants supplied for this exercise.
What the wrapper checks
Given a length n, the wrapper generates shapes in this order:
kind = (i % 3) as u8
a = 1.0 + (i % 7) as f64 * 0.5
b = 2.0 + (i % 5) as f64 * 0.25
It reports total, the names of the first up to six shapes, and alloc_ok. The tests include 100,000 shapes. The wrapper reserves Vec capacity before resetting its allocation counter, then samples the counter immediately after constructing all shapes. alloc_ok is true when that interval records no alloc, alloc_zeroed or realloc calls. Computing the total and formatting names happen after the sample.
That counter measures the observed build on the current toolchain. Optimizers can remove or combine allocations, so it is not a portable proof of an exact source-level allocation count. Keep the required inline representation and avoid allocation in your implementation, rather than trying to exploit the counter. See the GlobalAlloc contract.
Passing requires the specified behavior, alloc_ok in the supplied harness, successful compilation 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.