Reading — step 1 of 7
Learn
Defining Traits
A trait is a contract: any type that implements it provides these methods. Traits are Rust's interfaces (think Java interfaces or Haskell type classes), and they power both compile-time generics and runtime polymorphism.
Defining and implementing
trait Greet {
fn hello(&self) -> String; // required — no body
fn shout(&self) -> String { // default — overriding is optional
self.hello().to_uppercase()
}
}
struct English;
impl Greet for English {
fn hello(&self) -> String { String::from("hi") }
}
struct Klingon;
impl Greet for Klingon {
fn hello(&self) -> String { String::from("nuqneH") }
fn shout(&self) -> String { String::from("NUQNEH!") } // override
}
Skip a required method and the compiler names exactly what's missing:
error[E0046]: not all trait items implemented, missing: `hello`
And the impl's signatures must match the trait's exactly — fn hello(self) where the trait says fn hello(&self) is error[E0053]: method has an incompatible type for trait.
Using traits: generics (static dispatch)
fn announce<T: Greet>(thing: T) {
println!("{}", thing.hello());
}
fn complex<T>(x: T)
where
T: Greet + std::fmt::Debug, // multiple bounds read better in `where`
{
println!("{:?} says {}", x, x.hello());
}
T: Greet is a bound: only types implementing Greet may be passed. The compiler then monomorphizes — it generates a separate, fully-typed copy of announce for each concrete type used. Calls are direct and inlinable, zero runtime overhead; the cost is more generated code.
Using traits: trait objects (dynamic dispatch)
Generics fix the type at compile time. When the concrete type is only known at runtime — "circle or square, depending on input" — you need a trait object:
fn announce_all(things: Vec<Box<dyn Greet>>) {
for t in things {
println!("{}", t.hello()); // dispatched through a vtable
}
}
dyn Greet is unsized (an English and a Klingon can have different sizes), so it must live behind a pointer: Box<dyn Greet>, &dyn Greet. The trap:
fn make() -> dyn Greet { /* ... */ } // no Box
error[E0277]: the size for values of type `dyn Greet` cannot be known at compilation time
Method calls on a trait object go through a vtable — a small runtime cost that buys you one variable holding different concrete types. That's exactly what your exercise's main does: it builds Box::new(Circle {...}) or Box::new(Square {...}) and stores either in a single Box<dyn Shape>.
Your exercise
Implement Shape for both structs:
Circle { radius: f64 }— area is3.14 * self.radius * self.radius(the description says use3.14for π, literally)Square { side: f64 }— area isself.side * self.side
main is already written: it reads the kind and the dimension, builds the right shape as a Box<dyn Shape>, and prints s.area() with {:.2}.
TEST 2 feeds circle then 10 and expects exactly:
314.00
Mistakes the grader catches:
- Using
std::f64::consts::PI— more "correct" mathematically, but it prints314.16and fails TEST 2, which expects314.00. The grader wants the literal3.14. - Wrong method signature — anything but
fn area(&self) -> f64insideimpl Shape for ...breaks the trait match (E0053) or leaves the trait unimplemented (E0046), and theBox<dyn Shape>inmainwon't type-check. - Touching the
{:.2}—println!("{}", ...)prints25where TEST 1 expects25.00. Leavemainalone; you only need to add the twoimplblocks.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…