Skip to content

← Generics and Traits step 1 of 24

Easy Primitives

Traits as shared behaviour

A trait names a set of methods that different types can promise to provide. Generic functions use traits as bounds (fn f<T: Shape>(t: &T)), trait objects such as Box<dyn Shape> call trait methods on values whose concrete type is chosen at run time, and much of the standard library's shared behaviour is written as traits: Display for formatting, Iterator for iteration, PartialEq for ==, From for conversions and Add for +.

Here is the trait this exercise uses:

pub trait Shape {
    fn area(&self) -> f64;
}

It declares one required method. Any type that implements Shape must provide area, which borrows the value (&self) and returns an f64. The trait has no fields and gives area no default body, so it states what can be asked of a shape but not how to compute it. Each type keeps its own fields and implements the trait in a separate block:

pub struct Circle {
    pub radius: f64,
}

impl Shape for Circle {
    fn area(&self) -> f64 {
        std::f64::consts::PI * self.radius * self.radius
    }
}

The impl Shape for Circle block is written outside the struct Circle definition. Because a type and its trait implementations are separate items, you can implement your own trait for a type defined elsewhere, such as f64, and you can implement a standard trait such as Display for your own type. The orphan rule forbids most of the remaining combination: implementing a trait from another crate for a type from another crate, for example impl std::fmt::Display for Vec<i32>, is rejected with E0117. (The rule has narrow exceptions when one of your own types appears in the implementation, as in impl From<Circle> for String.)

Your task

pub fn areas(specs: Vec<(String, Vec<f64>)>) -> Vec<f64>

specs is a list of (tag, parameters) pairs. Return one area per pair, in the same order as the input. An empty input gives an empty output.

  • ("circle", [r]), with exactly one parameter: area π · r · r.
  • ("rect", [w, h]), with exactly two parameters: area w · h.
  • Anything else contributes 0.0. That includes an unknown tag such as "triangle", a tag that differs in case or spacing ("Circle", " rect"), and a known tag with the wrong number of parameters, such as ("rect", [3.0]) or ("circle", [2.0, 9.0]).

Tags are compared exactly. Parameters are finite numbers and are not validated: a negative radius still gives a non-negative area because it is squared, and a rectangle with one negative side gives a negative area. Return the computed values without rounding them; the grader compares numbers with a small tolerance.

The starter and its first error

The starter defines the trait, both structs and a complete areas function. For each pair, areas builds a Box<dyn Shape> (a heap-allocated value used only through the Shape trait), uses None when the pair matches neither shape, and calls area on the result. What is missing is the two method bodies: impl Shape for Circle {} and impl Shape for Rect {} are empty. Compile before changing anything. rustc reports this error once for each empty block:

error[E0046]: not all trait items implemented, missing: `area`

E0046 lists the required items that an impl block has not provided yet, so you can read it as a checklist. Add an area method to each block.

Two more errors are worth causing on purpose.

  • Misspell the method as fn are(&self) -> f64 inside one of the impl blocks. rustc reports E0407, "method are is not a member of trait Shape", and also E0046 for the area that is still missing. When both appear for the same block, a misspelled method name is a likely cause.
  • Call perimeter() on a Circle. rustc reports E0599, no method named perimeter found for struct Circle. Implementing Shape gives Circle only the methods that Shape declares, and nothing here declares perimeter.

Why the entry point takes tags and numbers

Each test case is stored as JSON, and a fixed harness converts it into Rust values before calling areas. The harness can build strings, numbers and vectors, but it does not know about Circle, Rect or Shape. So the entry point takes plain data, a String naming the shape plus a Vec<f64> of its parameters, and your code builds the shapes inside the function and returns plain f64 values.

To pass, the code must compile, return the expected areas and produce no warnings under cargo clippy -- -D warnings.