Traits (Interfaces)
Abstractions · Lesson 32
A trait is a list of methods a type promises to have. A trait can also include default methods, which a type gets without writing them. You use a trait to say what a function needs from its argument, instead of naming one type.
Coming from GoA trait plays the part of a Go interface: the list of methods a function needs. The difference is that a type must say impl Speaker for Robot; having the right methods is not enough. A trait can also have default methods, which Go interfaces lack. And because the impl is a separate block, you can implement your own trait for types you did not write, such as i32, which Go does not allow.
Coming from KotlinA trait is close to a Kotlin interface with default methods. The impl Speaker for Dog block lives apart from the type, so you can implement your trait for types you did not write, like i32. A Kotlin extension function adds a method to such a type but does not make it a Speaker.
Coming from PythonA trait is like a Protocol with default methods, but a type counts only once you write impl Speaker for Robot, and the compiler checks it, not a separate type checker. (A default in a Protocol reaches only the classes that name it as a base.)
Coming from TypeScriptA trait is like an interface plus the default method bodies you would otherwise put in an abstract class or a mixin. It's nominal, though: having a speak method isn't enough, the type must say impl Speaker for Robot.
Coming from JavaA trait is an interface, default methods included. A function that takes &dyn Speaker accepts any type that implements it, like a parameter of interface type, and T: Speaker is like <T extends Speaker>. The implementation lives in a separate impl Speaker for Dog block, so you can add your own trait to a type you didn't write, such as String, without a wrapper class.
Coming from C#A trait is an interface with default methods. You implement it in a separate impl Speaker for Robot block, which means you can add your trait to someone else's type. A default method can be called on the type itself, where a C# default interface method is only visible through the interface type.
Coming from CC has no interfaces; you'd build a struct of function pointers by hand. A trait is that pattern written down for the compiler, which checks that every type fills in every method. Used as &dyn Speaker, it is a data pointer plus a pointer to such a table.
Coming from C++A trait used through &dyn Speaker plays the part of an abstract base class with virtual methods, but a type does not inherit from it and a trait holds no data members. The same trait can also be a compile-time bound on a generic function, a lot like a C++20 concept, except that a type must say impl Speaker for Robot.
The quick comparison
trait Speaker { fn speak(&self) -> String;
fn shout(&self) -> String { // default method self.speak().to_uppercase() }}
struct Dog;struct Robot { id: u32,}
impl Speaker for Dog { fn speak(&self) -> String { String::from("woof") }}
impl Speaker for Robot { fn speak(&self) -> String { format!("beep {}", self.id) }}
fn introduce(s: &dyn Speaker) { // any type that implements Speaker println!("{} / {}", s.speak(), s.shout());}
fn main() { introduce(&Dog); introduce(&Robot { id: 7 });}woof / WOOF beep 7 / BEEP 7
package main
import ( "fmt" "strings")
type Speaker interface { Speak() string}
type Dog struct{}type Robot struct{ ID int }
func (Dog) Speak() string { return "woof" }func (r Robot) Speak() string { return fmt.Sprintf("beep %d", r.ID) }
func shout(s Speaker) string { return strings.ToUpper(s.Speak()) }
func introduce(s Speaker) { fmt.Println(s.Speak(), "/", shout(s))}
func main() { introduce(Dog{}) introduce(Robot{ID: 7})}woof / WOOF beep 7 / BEEP 7
interface Speaker { fun speak(): String fun shout(): String = speak().uppercase()}
object Dog : Speaker { override fun speak() = "woof"}
class Robot(val id: Int) : Speaker { override fun speak() = "beep $id"}
fun introduce(s: Speaker) { println("${s.speak()} / ${s.shout()}")}
fun main() { introduce(Dog) introduce(Robot(7))}woof / WOOF beep 7 / BEEP 7
from typing import Protocol
class Speaker(Protocol): def speak(self) -> str: ...
def shout(self) -> str: # default method return self.speak().upper()
class Dog(Speaker): def speak(self) -> str: return "woof"
class Robot(Speaker): def __init__(self, id: int) -> None: self.id = id
def speak(self) -> str: return f"beep {self.id}"
def introduce(s: Speaker) -> None: print(f"{s.speak()} / {s.shout()}")
introduce(Dog())introduce(Robot(7))woof / WOOF beep 7 / BEEP 7
// an abstract class can carry a default method, like a traitabstract class Speaker { abstract speak(): string;
shout(): string { // default method return this.speak().toUpperCase(); }}
class Dog extends Speaker { speak(): string { return "woof"; }}
class Robot extends Speaker { constructor(private id: number) { super(); } speak(): string { return `beep ${this.id}`; }}
function introduce(s: Speaker): void { console.log(`${s.speak()} / ${s.shout()}`);}
introduce(new Dog());introduce(new Robot(7));woof / WOOF beep 7 / BEEP 7
public class Main { interface Speaker { String speak();
default String shout() { // default method return speak().toUpperCase(); } }
record Dog() implements Speaker { public String speak() { return "woof"; } }
record Robot(int id) implements Speaker { public String speak() { return "beep " + id; } }
static void introduce(Speaker s) { System.out.println(s.speak() + " / " + s.shout()); }
public static void main(String[] args) { introduce(new Dog()); introduce(new Robot(7)); }}woof / WOOF beep 7 / BEEP 7
Introduce(new Dog());Introduce(new Robot(7));
static void Introduce(ISpeaker s) // any type that implements ISpeaker{ Console.WriteLine($"{s.Speak()} / {s.Shout()}");}
interface ISpeaker{ string Speak();
string Shout() => Speak().ToUpper(); // default method}
class Dog : ISpeaker{ public string Speak() => "woof";}
record Robot(uint Id) : ISpeaker{ public string Speak() => $"beep {Id}";}woof / WOOF beep 7 / BEEP 7
#include <ctype.h>#include <stdio.h>
// C has no traits: a "Speaker" is a struct of function pointersstruct Speaker { void (*speak)(const void *self, char *out, size_t n);};
struct Dog { int unused; };struct Robot { unsigned id; };
void dog_speak(const void *self, char *out, size_t n) { (void)self; snprintf(out, n, "woof");}
void robot_speak(const void *self, char *out, size_t n) { const struct Robot *r = self; snprintf(out, n, "beep %u", r->id);}
const struct Speaker DOG_SPEAKER = {dog_speak};const struct Speaker ROBOT_SPEAKER = {robot_speak};
// the "default method": works for any Speakervoid shout(const struct Speaker *sp, const void *self, char *out, size_t n) { sp->speak(self, out, n); for (char *p = out; *p; p++) *p = (char)toupper((unsigned char)*p);}
void introduce(const struct Speaker *sp, const void *self) { char a[64], b[64]; sp->speak(self, a, sizeof a); shout(sp, self, b, sizeof b); printf("%s / %s\n", a, b);}
int main(void) { struct Dog dog = {0}; struct Robot robot = {7}; introduce(&DOG_SPEAKER, &dog); introduce(&ROBOT_SPEAKER, &robot); return 0;}woof / WOOF beep 7 / BEEP 7
#include <algorithm>#include <cctype>#include <format>#include <iostream>#include <string>
class Speaker {public: virtual ~Speaker() = default; virtual std::string speak() const = 0;
std::string shout() const { // shared method built on speak() std::string s = speak(); std::ranges::transform(s, s.begin(), [](unsigned char c) { return std::toupper(c); }); return s; }};
class Dog : public Speaker {public: std::string speak() const override { return "woof"; }};
class Robot : public Speaker {public: explicit Robot(unsigned id) : id_(id) {} std::string speak() const override { return std::format("beep {}", id_); }
private: unsigned id_;};
void introduce(const Speaker& s) { // any class that derives from Speaker std::cout << s.speak() << " / " << s.shout() << "\n";}
int main() { introduce(Dog{}); introduce(Robot{7});}woof / WOOF beep 7 / BEEP 7
What's different
&dyn Speakermeans "a reference to some type that implements Speaker, decided at runtime". The rightspeakis looked up while the program runs.- There is also a compile-time version:
fn introduce(s: &impl Speaker). The compiler knows the exact type at each call, so it needs no lookup. Usedynwhen you need a list of mixed types. The generics lesson covers the first, the trait objects lesson the second. format!works likeprintln!, but returns the text as a String instead of printing it.
Having the method but not saying so
Looks fine: in Go, Python and TypeScript it runs fine. Rust won't compile it.
Works in Go. In Rust it's just a rule.Kotlin refuses this too. Same idea, different words.Works in Python. In Rust it's just a rule.Works in TypeScript. In Rust it's just a rule.Java refuses this too. Same idea, different words.C# refuses this too. Same idea, different words.No direct equivalent in C. Just a Rust rule.C++ refuses this too. Same idea, different words.trait Speaker { fn speak(&self) -> String;}
struct Robot { id: u32,}
impl Robot { fn speak(&self) -> String { format!("beep {}", self.id) }}
fn introduce(s: &dyn Speaker) { println!("{}", s.speak());}
fn main() { introduce(&Robot { id: 7 });}package main
import "fmt"
type Speaker interface{ Speak() string }
type Robot struct{ ID int }
func (r Robot) Speak() string { return fmt.Sprintf("beep %d", r.ID) }
func introduce(s Speaker) { fmt.Println(s.Speak()) }
func main() { introduce(Robot{ID: 7})}beep 7
Kotlin refuses this too: Robot has to declare : Speaker before it can be passed as one.
interface Speaker { fun speak(): String}
class Robot(val id: Int) { // never says ": Speaker" fun speak(): String = "beep $id"}
fun introduce(s: Speaker) = println(s.speak())
fun main() { introduce(Robot(7))}Main.kt:12:15: error: argument type mismatch: actual type is 'Robot', but 'Speaker' was expected.
introduce(Robot(7))
^^^^^^^^class Robot: def __init__(self, id: int): self.id = id
def speak(self) -> str: return f"beep {self.id}"
def introduce(s): print(s.speak())
introduce(Robot(7))beep 7
interface Speaker { speak(): string;}
class Robot { // never says "implements Speaker" constructor(private id: number) {} speak(): string { return `beep ${this.id}`; }}
function introduce(s: Speaker): void { console.log(s.speak());}
introduce(new Robot(7));beep 7
Java refuses this too: Robot has a speak method but never says implements Speaker, so it is not a Speaker.
public class Main { interface Speaker { String speak(); }
record Robot(int id) { String speak() { return "beep " + id; } }
static void introduce(Speaker s) { System.out.println(s.speak()); }
public static void main(String[] args) { introduce(new Robot(7)); }}Main.java:17: error: incompatible types: Robot cannot be converted to Speaker
introduce(new Robot(7));
^
Note: Some messages have been simplified; recompile with -Xdiags:verbose to get full output
1 errorC# refuses this too: having a Speak method is not enough, the class must say : ISpeaker (CS1503). Interfaces are opt-in by name in C#, as in Java.
Introduce(new Robot(7));
static void Introduce(ISpeaker s){ Console.WriteLine(s.Speak());}
interface ISpeaker{ string Speak();}
record Robot(uint Id) // has Speak, but never says ": ISpeaker"{ public string Speak() => $"beep {Id}";}Program.cs(1,11): error CS1503: Argument 1: cannot convert from 'Robot' to 'ISpeaker'
C has no traits or interfaces. A C "Speaker" is a struct of function pointers you fill in by hand, so there is no declaration to forget.
C++ refuses this too: Robot doesn't inherit from Speaker, so it can't be passed as a const Speaker&. A template or concept would accept it, since those only check that speak() exists.
#include <format>#include <iostream>#include <string>
class Speaker {public: virtual ~Speaker() = default; virtual std::string speak() const = 0;};
class Robot {public: unsigned id; std::string speak() const { return std::format("beep {}", id); }};
void introduce(const Speaker& s) { std::cout << s.speak() << "\n";}
int main() { introduce(Robot{7});}main.cpp: In function ‘int main()’:
main.cpp:22:15: error: invalid initialization of reference of type ‘const Speaker&’ from expression of type ‘Robot’
22 | introduce(Robot{7});
| ^~~~~~~~
main.cpp:17:31: note: in passing argument 1 of ‘void introduce(const Speaker&)’
17 | void introduce(const Speaker& s) {
| ~~~~~~~~~~~~~~~^What does the compiler say? Guess first, then open.
error[E0277]: the trait bound `Robot: Speaker` is not satisfied --> src/main.rs:20:15 | 20 | introduce(&Robot { id: 7 }); | ^^^^^^^^^^^^^^^^ unsatisfied trait bound | help: the trait `Speaker` is not implemented for `Robot` --> src/main.rs:5:1 | 5 | struct Robot { | ^^^^^^^^^^^^ help: this trait has no implementations, consider adding one --> src/main.rs:1:1 | 1 | trait Speaker { | ^^^^^^^^^^^^^ = note: required for the cast from `&Robot` to `&dyn Speaker`
Go runs this and prints beep 7. Having a Speak method is enough to count as a Speaker, with nothing to declare.
Kotlin refuses this too: Robot has to declare : Speaker before it can be passed as one.
Python prints beep 7. Python uses duck typing: anything with a speak method works, with no need to declare that Robot is a Speaker.
Prints beep 7. TypeScript checks the shape: Robot has a matching speak, so it counts as a Speaker without saying implements.
Java refuses this too: Robot has a speak method but never says implements Speaker, so it is not a Speaker.
C# refuses this too: having a Speak method is not enough, the class must say : ISpeaker (CS1503). Interfaces are opt-in by name in C#, as in Java.
C has no traits or interfaces. A C "Speaker" is a struct of function pointers you fill in by hand, so there is no declaration to forget.
C++ refuses this too: Robot doesn't inherit from Speaker, so it can't be passed as a const Speaker&. A template or concept would accept it, since those only check that speak() exists.
Rust wants you to say it: impl Speaker for Robot. A method with a matching name is not enough.
One type can then have two methods with the same name that mean different things: the lesson's Car has a start from Vehicle and another from Appliance, and a call says which. It also lets you add a trait to a type later, in a separate place, which is how impl Speaker for i32 works.
The fix
Compiles and runs.
trait Speaker { fn speak(&self) -> String;}
struct Robot { id: u32,}
impl Speaker for Robot { fn speak(&self) -> String { format!("beep {}", self.id) }}
fn introduce(s: &dyn Speaker) { println!("{}", s.speak());}
fn main() { introduce(&Robot { id: 7 });}beep 7
The toolkit
A trait names a set of methods. A type that has those methods, and says so, can be used wherever the trait is asked for. It is what Rust uses where other languages use interfaces, abstract base classes or protocols. Outputs come from Linux on 64-bit ARM, Rust 1.99.1.
Defining a trait and implementing it
trait Speaker { fn speak(&self) -> String; // required: every type writes its own
fn shout(&self) -> String { // default: built on the required method self.speak().to_uppercase() }}
struct Dog;struct Robot { id: u32,}struct Parrot { phrase: String,}
impl Speaker for Dog { fn speak(&self) -> String { String::from("woof") }}
impl Speaker for Robot { fn speak(&self) -> String { format!("beep {}", self.id) }}
impl Speaker for Parrot { fn speak(&self) -> String { self.phrase.clone() }
fn shout(&self) -> String { // replaces the default format!("{}!!", self.speak()) }}
fn main() { let robot = Robot { id: 7 }; let parrot = Parrot { phrase: String::from("hello"), }; println!("dog.speak(): {}", Dog.speak()); println!("dog.shout(): {}", Dog.shout()); println!("robot.shout(): {}", robot.shout()); println!("parrot.shout(): {}", parrot.shout());}dog.speak(): woof dog.shout(): WOOF robot.shout(): BEEP 7 parrot.shout(): hello!!
trait Speaker { ... }lists methods.speakhas no body: every type that implements the trait must write one.shouthas a body: a default method, used unless a type replaces it.impl Speaker for Dog { ... }is a block of its own, apart from the struct. The trait is not part of the type’s definition, so it can be added later, and in another place.Parrotreplacesshout, andDogandRobotkeep the default. The default callsself.speak(), so it works for any type.- The first parameter means what it does in Methods.
- Once the trait is in scope,
Dog.speak()is an ordinary method call (scope, below).
A replacement cannot call the default it replaces. There is no super. Writing Speaker::shout(self) inside Parrot’s shout names Parrot’s own shout, so it calls itself until the stack is gone:
trait Speaker { fn speak(&self) -> String;
fn shout(&self) -> String { self.speak().to_uppercase() }}
struct Parrot;
impl Speaker for Parrot { fn speak(&self) -> String { String::from("hello") }
// There is no `super` call. This names Parrot's own shout again. #[allow(unconditional_recursion)] fn shout(&self) -> String { format!("{}!!", Speaker::shout(self)) }}
fn main() { println!("{}", Parrot.shout());}thread 'main' (1) has overflowed its stack fatal runtime error: stack overflow, aborting
Move the shared part into another method or a plain function.
A trait can hold more than methods:
| A trait can list | Example | Shown in |
|---|---|---|
| Methods, with or without a default body | fn speak(&self) -> String; |
first-trait.rs above |
Associated functions, with no self |
fn make() -> String; |
two traits |
| Associated types and constants | type Item;, const LIMIT: u32; |
associated items |
| Fields | not allowed | below |
trait Named { name: String,}
fn main() {}error: expected one of `!` or `::`, found `:` --> src/main.rs:2:9 | 1 | trait Named { | - while parsing this item list starting here 2 | name: String, | ^ expected one of `!` or `::` 3 | } | - the item list ends here
A trait body has no fields. When the data matters, the trait asks for a method (fn name(&self) -> &str) and each type decides where the data is stored.
Asking for a trait: bounds
A function says which trait it needs from its argument. That requirement is a bound.
use std::fmt::Display;
trait Speaker { fn speak(&self) -> String;}
struct Dog;struct Robot { id: u32,}
impl Speaker for Dog { fn speak(&self) -> String { String::from("woof") }}
impl Speaker for Robot { fn speak(&self) -> String { format!("beep {}", self.id) }}
impl Display for Robot { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "robot #{}", self.id) }}
// 1. a type parameter with a boundfn intro_a<T: Speaker>(s: &T) -> String { format!("a: {}", s.speak())}
// 2. the same, written as `impl Trait` in the parameterfn intro_b(s: &impl Speaker) -> String { format!("b: {}", s.speak())}
// 3. the same again, with the bound moved into a where clausefn intro_c<T>(s: &T) -> Stringwhere T: Speaker,{ format!("c: {}", s.speak())}
// several bounds are joined with +fn label<T: Speaker + Display>(s: &T) -> String { format!("{} says {}", s, s.speak())}
fn main() { println!("{}", intro_a(&Dog)); println!("{}", intro_b(&Dog)); println!("{}", intro_c(&Robot { id: 7 })); println!("{}", label(&Robot { id: 7 }));}a: woof b: woof c: beep 7 robot #7 says beep 7
T: Speakerreads “T is some type that implementsSpeaker”.Tis a type parameter, a placeholder the caller fills in (Generics).&impl Speakeris the same bound without namingT.where T: Speakeris the same bound moved after the signature, which helps when a signature gets long.+asks for several traits:T: Speaker + Display.- Inside the function, the bounds decide which methods
Thas:intro_acan callspeakbecause of its bound, and nothing the bound does not mention.
Each call to intro_a uses one concrete type, chosen by the caller and known when the program is compiled: the compiler produces a copy of the function for each type. That is static dispatch. Going deeper shows the other kind.
When the type has no impl
Pass a type that has not implemented the trait, and the compiler stops at the call:
trait Speaker { fn speak(&self) -> String;}
struct Dog;struct Rock;
impl Speaker for Dog { fn speak(&self) -> String { String::from("woof") }}
fn introduce<T: Speaker>(s: &T) { println!("{}", s.speak());}
fn main() { introduce(&Dog); introduce(&Rock);}error[E0277]: the trait bound `Rock: Speaker` is not satisfied --> src/main.rs:20:15 | 20 | introduce(&Rock); | --------- ^^^^^ unsatisfied trait bound | | | required by a bound introduced by this call | help: the trait `Speaker` is not implemented for `Rock` --> src/main.rs:6:1 | 6 | struct Rock; | ^^^^^^^^^^^ help: the trait `Speaker` is implemented for `Dog` --> src/main.rs:8:1 | 8 | impl Speaker for Dog { | ^^^^^^^^^^^^^^^^^^^^ note: required by a bound in `introduce` --> src/main.rs:14:17 | 14 | fn introduce<T: Speaker>(s: &T) { | ^^^^^^^ required by this bound in `introduce`
E0277 means “this type lacks something the code asks of it”: here the T: Speaker bound. Calling the method directly gives a different error:
trait Speaker { fn speak(&self) -> String;}
struct Dog;struct Rock;
impl Speaker for Dog { fn speak(&self) -> String { String::from("woof") }}
fn main() { println!("{}", Dog.speak()); println!("{}", Rock.speak());}error[E0599]: no method named `speak` found for struct `Rock` in the current scope --> src/main.rs:16:25 | 6 | struct Rock; | ----------- method `speak` not found for this struct ... 16 | println!("{}", Rock.speak()); | ^^^^^ method not found in `Rock` | = help: items from traits can only be used if the trait is implemented and in scope note: `Speaker` defines an item `speak`, perhaps you need to implement it --> src/main.rs:1:1 | 1 | trait Speaker { | ^^^^^^^^^^^^^
E0599 is “no such method”. The hint names the trait that defines speak, so you can implement it. An impl that leaves out a required method is refused with E0046:
trait Speaker { fn speak(&self) -> String; fn volume(&self) -> u8;}
struct Dog;
impl Speaker for Dog { fn speak(&self) -> String { String::from("woof") }}
fn main() { println!("{}", Dog.speak());}error[E0046]: not all trait items implemented, missing: `volume` --> src/main.rs:8:1 | 3 | fn volume(&self) -> u8; | ----------------------- `volume` from trait ... 8 | impl Speaker for Dog { | ^^^^^^^^^^^^^^^^^^^^ missing `volume` in implementation
A trait’s methods and scope
To call a trait’s method on a concrete type, the trait must be in scope in the module where you write the call. A mod block in the same file is another scope (a named container of code, covered in Modules):
mod zoo { pub trait Speaker { fn speak(&self) -> String; }
pub struct Dog;
impl Speaker for Dog { fn speak(&self) -> String { String::from("woof") } }}
fn main() { let dog = zoo::Dog; println!("{}", dog.speak());}error[E0599]: no method named `speak` found for struct `Dog` in the current scope --> src/main.rs:17:24 | 3 | fn speak(&self) -> String; | ----- the method is available for `Dog` here ... 6 | pub struct Dog; | -------------- method `speak` not found for this struct ... 17 | println!("{}", dog.speak()); | ^^^^^ method not found in `Dog` | = help: items from traits can only be used if the trait is in scope help: trait `Speaker` which provides `speak` is implemented but not in scope; perhaps you want to import it | 1 + use crate::zoo::Speaker;
The impl exists, and the compiler suggests use crate::zoo::Speaker;. Two cases need no import: a generic function with a bound T: zoo::Speaker, and a &dyn zoo::Speaker. The bound names the trait, so its methods are reachable. And use ... as _; brings a trait into scope for method calls without taking its name:
mod zoo { pub trait Speaker { fn speak(&self) -> String; }
pub struct Dog;
impl Speaker for Dog { fn speak(&self) -> String { String::from("woof") } }}
// No `use` here: the bound names the trait, so t.speak() is reachable.fn generic<T: zoo::Speaker>(t: &T) -> String { t.speak()}
// The same for a trait object.fn dynamic(t: &dyn zoo::Speaker) -> String { t.speak()}
mod kennel { // `as _` brings the trait into scope for method calls without taking its name. use crate::zoo::Speaker as _;
pub fn call() -> String { crate::zoo::Dog.speak() }}
fn main() { println!("generic: {}", generic(&zoo::Dog)); println!("dyn: {}", dynamic(&zoo::Dog)); println!("kennel: {}", kennel::call());}generic: woof dyn: woof kennel: woof
The prelude (the names every module gets without writing use) already includes Iterator, Clone, Default and other common traits, which is why you do not usually import those. Its page lists them.
Two traits, one method name
Two traits can have a method with the same name. A type’s own method wins over a trait’s. To pick a trait yourself, name it:
trait Vehicle { fn start(&self) -> String;}
trait Appliance { fn start(&self) -> String; fn make() -> String; // no self: an associated function}
struct Car;
impl Car { fn start(&self) -> String { String::from("Car::start (its own method)") }}
impl Vehicle for Car { fn start(&self) -> String { String::from("Vehicle::start") }}
impl Appliance for Car { fn start(&self) -> String { String::from("Appliance::start") }
fn make() -> String { String::from("Appliance::make") }}
fn main() { let car = Car; println!("car.start(): {}", car.start()); println!("Vehicle::start(&car): {}", Vehicle::start(&car)); println!("Appliance::start(&car): {}", Appliance::start(&car)); println!("<Car as Appliance>::make(): {}", <Car as Appliance>::make());}car.start(): Car::start (its own method) Vehicle::start(&car): Vehicle::start Appliance::start(&car): Appliance::start <Car as Appliance>::make(): Appliance::make
Vehicle::start(&car)calls theVehicleversion, passing the receiver by hand.<Car as Appliance>::make()is the fully qualified form. With noselfthere is no receiver to guide the choice, so you name the type and the trait.
A type may implement both traits, even when both have a default start. The error comes only from a bare call that could mean either:
trait Vehicle { fn start(&self) -> String { String::from("Vehicle::start") }}
trait Appliance { fn start(&self) -> String { String::from("Appliance::start") }}
struct Car;
// Both impls are accepted, even though both traits have a default `start`.impl Vehicle for Car {}impl Appliance for Car {}
fn main() { println!("{}", Car.start());}error[E0034]: multiple applicable items in scope --> src/main.rs:20:24 | 20 | println!("{}", Car.start()); | ^^^^^ multiple `start` found | note: candidate #1 is defined in an impl of the trait `Appliance` for the type `Car` --> src/main.rs:8:5 | 8 | fn start(&self) -> String { | ^^^^^^^^^^^^^^^^^^^^^^^^^ note: candidate #2 is defined in an impl of the trait `Vehicle` for the type `Car` --> src/main.rs:2:5 | 2 | fn start(&self) -> String { | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: disambiguate the method for candidate #1 | 20 - println!("{}", Car.start()); 20 + println!("{}", Appliance::start(&Car)); | help: disambiguate the method for candidate #2 | 20 - println!("{}", Car.start()); 20 + println!("{}", Vehicle::start(&Car));
Traits for types you did not write
You can implement your trait for any type, including i32, &str and Vec<T>:
trait Describe { fn describe(&self) -> String;}
impl Describe for i32 { fn describe(&self) -> String { format!("the number {}", self) }}
impl Describe for &str { fn describe(&self) -> String { format!("the text {:?}", self) }}
impl<T: Describe> Describe for Vec<T> { fn describe(&self) -> String { let parts: Vec<String> = self.iter().map(|item| item.describe()).collect(); format!("a list of [{}]", parts.join(", ")) }}
fn main() { println!("{}", 7.describe()); println!("{}", "hi".describe()); println!("{}", vec![1, 2].describe());}the number 7 the text "hi" a list of [the number 1, the number 2]
impl<T: Describe> Describe for Vec<T> says “a Vec of describable things is describable”. A blanket impl covers every type that meets a bound:
use std::fmt::Display;
trait Shout { fn shout(&self) -> String;}
// Every type that implements Display gets Shout, with no per-type impl.impl<T: Display> Shout for T { fn shout(&self) -> String { self.to_string().to_uppercase() + "!" }}
struct Robot { id: u32,}
impl Display for Robot { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "robot {}", self.id) }}
fn main() { println!("&str: {}", "hello".shout()); println!("i32: {}", 42.shout()); println!("Robot: {}", Robot { id: 7 }.shout());}&str: HELLO! i32: 42! Robot: ROBOT 7!
impl<T: Display> Shout for T gave Shout to &str, i32 and Robot at once. The standard library’s ToString reaches every Display type this way.
There is a limit. You may write impl Trait for Type if the trait or the type is defined in your own crate (your package’s code). This is the orphan rule:
| Trait | Type | Allowed? | Shown by |
|---|---|---|---|
| yours | yours | yes | first-trait.rs |
| yours | foreign (i32, Vec<T>) |
yes | foreign.rs |
foreign (Display) |
yours | yes | newtype.rs below |
foreign (Display) |
foreign (Vec<i32>) |
no, unless a type of yours is among the trait’s own type parameters (next paragraph) | orphan.fail.rs below |
use std::fmt;
impl fmt::Display for Vec<i32> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{} numbers", self.len()) }}
fn main() { println!("{}", vec![1, 2, 3]);}error[E0117]: only traits defined in the current crate can be implemented for types defined outside of the crate --> src/main.rs:3:1 | 3 | impl fmt::Display for Vec<i32> { | ^^^^^^^^^^^^^^^^^^^^^^-------- | | | `Vec` is not defined in the current crate | = note: impl doesn't have any local type before any uncovered type parameters = note: for more information see https://doc.rust-lang.org/reference/items/implementations.html#orphan-rules = note: define and implement a trait or new type instead
E0117 is the orphan rule. Traits can take type parameters (From<T> does), and impl From<Meters> for f64 is accepted when Meters is your type, because it appears among the trait’s parameters. The Reference states the full rule.
The way around is a newtype: a one-field struct that wraps the foreign type. The wrapper is yours, so the impl is allowed:
use std::fmt;
struct Numbers(Vec<i32>); // a wrapper type that belongs to this crate
impl fmt::Display for Numbers { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{} numbers", self.0.len()) }}
fn main() { println!("{}", Numbers(vec![1, 2, 3]));}3 numbers
Supertraits
A trait can require another trait. Pet: Speaker reads “to be a Pet, a type must also be a Speaker”, so Pet’s methods may call speak:
trait Speaker { fn speak(&self) -> String;}
// A Pet must also be a Speaker: Pet's own methods may call speak().trait Pet: Speaker { fn name(&self) -> String;
fn introduce(&self) -> String { format!("{} says {}", self.name(), self.speak()) }}
struct Dog;
impl Speaker for Dog { fn speak(&self) -> String { String::from("woof") }}
impl Pet for Dog { fn name(&self) -> String { String::from("Rex") }}
// A bound on Pet also gives access to Speaker's methods.fn loud<P: Pet>(p: &P) -> String { p.speak().to_uppercase()}
fn main() { println!("introduce(): {}", Dog.introduce()); println!("loud(): {}", loud(&Dog));}introduce(): Rex says woof loud(): WOOF
A bound on Pet gives access to Speaker’s methods too, as loud shows. This is not inheritance: it is a promise about what else the type implements, and breaking it gives E0277 again:
trait Speaker { fn speak(&self) -> String;}
trait Pet: Speaker { fn name(&self) -> String;}
struct Cat;
impl Pet for Cat { fn name(&self) -> String { String::from("Tom") }}
fn main() { println!("{}", Cat.name());}error[E0277]: the trait bound `Cat: Speaker` is not satisfied --> src/main.rs:11:14 | 11 | impl Pet for Cat { | ^^^ unsatisfied trait bound | help: the trait `Speaker` is not implemented for `Cat` --> src/main.rs:9:1 | 9 | struct Cat; | ^^^^^^^^^^ help: this trait has no implementations, consider adding one --> src/main.rs:1:1 | 1 | trait Speaker { | ^^^^^^^^^^^^^ note: required by a bound in `Pet` --> src/main.rs:5:12 | 5 | trait Pet: Speaker { | ^^^^^^^ required by this bound in `Pet` error[E0277]: the trait bound `Cat: Speaker` is not satisfied --> src/main.rs:18:24 | 18 | println!("{}", Cat.name()); | ^^^^ unsatisfied trait bound | help: the trait `Speaker` is not implemented for `Cat` --> src/main.rs:9:1 | 9 | struct Cat; | ^^^^^^^^^^ help: this trait has no implementations, consider adding one --> src/main.rs:1:1 | 1 | trait Speaker { | ^^^^^^^^^^^^^ note: required by a bound in `Pet::name` --> src/main.rs:5:12 | 5 | trait Pet: Speaker { | ^^^^^^^ required by this bound in `Pet::name` 6 | fn name(&self) -> String; | ---- required by a bound in this associated function
Associated types and constants
A trait can leave a type or a constant for each implementing type to choose:
use std::any::type_name;
trait Container { type Item; // chosen by each implementing type const KIND: &'static str; // a constant, also chosen per type
fn first(&self) -> Option<&Self::Item>;}
struct Names(Vec<String>);struct Counts(Vec<u32>);
impl Container for Names { type Item = String; const KIND: &'static str = "names";
fn first(&self) -> Option<&String> { self.0.first() }}
impl Container for Counts { type Item = u32; const KIND: &'static str = "counts";
fn first(&self) -> Option<&u32> { self.0.first() }}
fn show<C: Container>(c: &C) { println!( "{}: Item is {}, first is {}", C::KIND, type_name::<C::Item>(), c.first().is_some() );}
fn main() { show(&Names(vec![String::from("ana")])); show(&Counts(vec![]));}names: Item is alloc::string::String, first is true counts: Item is u32, first is false
C::Item means “the Item of whichever C this is”, and C::KIND reads the constant. &'static str is the type of a string literal such as "names" (Lifetimes explains 'static). alloc::string::String is the full path of the standard library’s String.
Iterator has a type Item like this (Iterators writes one). In Java, Iterator<T> lets the user of the interface pick T. Here each implementing type writes its Item once, so the trait is plain Iterator.
Returning “something that implements a trait”
impl Speaker also works as a return type. It hides the type but does not make it vary:
trait Speaker { fn speak(&self) -> String;}
struct Dog;struct Robot;
impl Speaker for Dog { fn speak(&self) -> String { String::from("woof") }}
impl Speaker for Robot { fn speak(&self) -> String { String::from("beep") }}
fn pick(dog: bool) -> impl Speaker { if dog { Dog } else { Robot }}
fn main() { println!("{}", pick(true).speak());}error[E0308]: `if` and `else` have incompatible types --> src/main.rs:21:27 | 21 | if dog { Dog } else { Robot } | --- ^^^^^ expected `Dog`, found `Robot` | | | expected because of this | help: you could change the return type to be a boxed trait object | 20 - fn pick(dog: bool) -> impl Speaker { 20 + fn pick(dog: bool) -> Box<dyn Speaker> { | help: if you change the return type to expect trait objects, box the returned expressions | 21 | if dog { Box::new(Dog) } else { Box::new(Robot) } | +++++++++ + +++++++++ +
impl Speaker means one concrete type that the compiler knows and the caller does not name. When the answer depends on run-time data, the compiler suggests a trait object: Box<dyn Speaker>, a pointer to any value that implements the trait. Box<T> is an owned pointer to a value on the heap, and dyn marks that the method is looked up while the program runs (Trait objects):
trait Speaker { fn speak(&self) -> String;}
struct Dog;struct Robot;
impl Speaker for Dog { fn speak(&self) -> String { String::from("woof") }}
impl Speaker for Robot { fn speak(&self) -> String { String::from("beep") }}
// One concrete type per function: the caller sees only "a Speaker".fn make_dog() -> impl Speaker { Dog}
// Different types chosen at run time need a trait object.fn pick(dog: bool) -> Box<dyn Speaker> { if dog { Box::new(Dog) } else { Box::new(Robot) }}
fn main() { println!("make_dog(): {}", make_dog().speak()); println!("pick(false): {}", pick(false).speak());}make_dog(): woof pick(false): beep
A trait is not a type, so its bare name cannot stand where a type goes. Programmers used to interfaces write this first:
trait Speaker { fn speak(&self) -> String;}
// A trait is not a type: its bare name is refused in all three places.fn param(s: &Speaker) -> usize { s.speak().len()}
fn list(v: Vec<Speaker>) -> usize { v.len()}
fn result() -> Speaker { todo!()}
fn main() {}error[E0782]: expected a type, found a trait --> src/main.rs:6:14 | 6 | fn param(s: &Speaker) -> usize { | ^^^^^^^ | help: use a new generic type parameter, constrained by `Speaker` | 6 - fn param(s: &Speaker) -> usize { 6 + fn param<T: Speaker>(s: &T) -> usize { | help: you can also use an opaque type, but users won't be able to specify the type parameter when calling the `fn`, having to rely exclusively on type inference | 6 | fn param(s: &impl Speaker) -> usize { | ++++ help: alternatively, use a trait object to accept any type that implements `Speaker`, accessing its methods at runtime using dynamic dispatch | 6 | fn param(s: &dyn Speaker) -> usize { | +++ error[E0782]: expected a type, found a trait --> src/main.rs:10:16 | 10 | fn list(v: Vec<Speaker>) -> usize { | ^^^^^^^ | help: you can add the `dyn` keyword if you want a trait object | 10 | fn list(v: Vec<dyn Speaker>) -> usize { | +++ error[E0782]: expected a type, found a trait --> src/main.rs:14:16 | 14 | fn result() -> Speaker { | ^^^^^^^ | help: use `impl Speaker` to return an opaque type, as long as you return a single underlying type | 14 | fn result() -> impl Speaker { | ++++ help: alternatively, you can return an owned trait object | 14 | fn result() -> Box<dyn Speaker> { | +++++++ +
E0782 lists the fixes: a bound, impl, or dyn. Trait objects explains why dyn is needed.
| Spelling | Where | The type is |
|---|---|---|
T: Speaker, &impl Speaker |
parameter | any one type, chosen by the caller |
-> impl Speaker |
result | one type, chosen by the function |
&dyn Speaker, Box<dyn Speaker> |
parameter, result, or inside a collection | any type, looked up at run time |
Translating from what you know
Going deeper
Static and dynamic dispatch in one program
use std::any::type_name;
trait Speaker { fn speak(&self) -> String;}
struct Dog;struct Robot { id: u32,}
impl Speaker for Dog { fn speak(&self) -> String { String::from("woof") }}
impl Speaker for Robot { fn speak(&self) -> String { format!("beep {}", self.id) }}
// Generic: T is one concrete type, known when the program is compiled.fn generic<T: Speaker>(s: &T) { println!("generic: T = {}, says {}", type_name::<T>(), s.speak());}
fn main() { generic(&Dog); generic(&Robot { id: 7 });
// dyn: one list holding different types; the method is looked up while running. let crowd: Vec<Box<dyn Speaker>> = vec![Box::new(Dog), Box::new(Robot { id: 7 })]; for s in &crowd { println!("dyn: {}", s.speak()); }}generic: T = main::Dog, says woof generic: T = main::Robot, says beep 7 dyn: woof dyn: beep 7
genericprints a differentTfor each call. The compiler knows whichspeakto call, because each call is for one type. This is static dispatch, and the generics lesson explains how the compiler produces a version for each type. (The texttype_nameprints is not guaranteed, so use it to look, not to compare.)- The
Vec<Box<dyn Speaker>>holds aDogand aRobotside by side, which aVec<T>cannot. The method is looked up while the program runs. This is dynamic dispatch, and Trait objects explains the lookup.
A plain Vec of two different types is refused for the same reason (all elements of a Vec<T> are one type):
trait Speaker { fn speak(&self) -> String;}
struct Dog;struct Robot;
impl Speaker for Dog { fn speak(&self) -> String { String::from("woof") }}
impl Speaker for Robot { fn speak(&self) -> String { String::from("beep") }}
fn main() { let crowd = vec![Dog, Robot]; println!("{}", crowd.len());}error[E0308]: mismatched types --> src/main.rs:21:27 | 21 | let crowd = vec![Dog, Robot]; | ^^^^^ expected `Dog`, found `Robot`
Why the orphan rule exists
Without it, two crates could implement the same trait for the same type, and Rust would not know which to use (the property that prevents this is coherence). The same reasoning applies in your own crate: two impls that could both apply to one type are refused (E0119):
use std::fmt::Display;
trait Describe { fn describe(&self) -> String;}
impl<T: Display> Describe for T { fn describe(&self) -> String { format!("something printable: {}", self) }}
impl Describe for i32 { fn describe(&self) -> String { format!("the number {}", self) }}
fn main() { println!("{}", 7.describe());}error[E0119]: conflicting implementations of trait `Describe` for type `i32` --> src/main.rs:13:1 | 7 | impl<T: Display> Describe for T { | ------------------------------- first implementation here ... 13 | impl Describe for i32 { | ^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `i32`
i32 implements Display, so the blanket impl already covers it. The rule’s price is boilerplate: a newtype forwards the methods you need by hand (advanced traits covers the pattern).
derive is a trait impl
Each name in #[derive(Debug, Clone)] is a trait, and the compiler writes the impl block from the fields (Structs). Write one by hand when the generated one is wrong or no derive exists, as for Display; Common traits says which to derive. A trait can also have no methods at all, like Copy: implementing it is a statement about the type (std::marker lists such traits).
A trait or an enum?
Both answer “this value is one of several kinds”; they differ in who can add a kind.
| An enum | A trait | |
|---|---|---|
| The kinds | listed in one place; adding one means editing the enum | any crate can add a type with an impl |
match over the kinds |
the compiler checks that every case is covered | not directly: the code sees the trait’s methods, not the concrete type |
When you know every kind (the commands of a protocol), an enum lets the compiler check each place that handles them. When other code should add kinds (plugins, a library that calls your code), use a trait. Neither is better in general.
Practice
Try it
Each exercise is a small program with checks in main. Open it in the Playground, replace the todo!(), and run until it prints all checks passed.
Shapes with a default method
Fill in the Shape implementations and the default describe. Triangle’s impl is given and has no describe, which must still work, so write it once, in the trait. Then write biggest (the position of the largest item, or None), which takes a slice of one type, and total_area, which takes a mixed list of &dyn Shape.
trait Shape { fn area(&self) -> f64; fn name(&self) -> String;
/// Default: "<name> with area <area>", the area with two decimals. fn describe(&self) -> String { todo!("format self.name() and self.area() with {{:.2}}") }}
struct Square(f64);struct Circle(f64);struct Triangle { base: f64, height: f64,}
// Area of a square is side * side; of a circle, PI * radius * radius.// Names are "square" and "circle".impl Shape for Square { fn area(&self) -> f64 { todo!("side * side") } fn name(&self) -> String { todo!("the name") }}
impl Shape for Circle { fn area(&self) -> f64 { todo!("PI * radius * radius, with std::f64::consts::PI") } fn name(&self) -> String { todo!("the name") }}
// Triangle gives only the two required methods and has no describe().// Its describe() must still work, so the default belongs in the trait.impl Shape for Triangle { fn area(&self) -> f64 { self.base * self.height / 2.0 } fn name(&self) -> String { String::from("triangle") }}
/// The position of the item with the largest area, or None for an empty slice./// (If two tie, either position is fine.)fn biggest<T: Shape>(items: &[T]) -> Option<usize> { todo!("compare areas with total_cmp; return the winner's position")}
/// The total area of a mixed list: a Square next to a Circle./// `&dyn Shape` is a reference to any type that implements Shape.fn total_area(items: &[&dyn Shape]) -> f64 { todo!("a slice of &dyn Shape holds different types; add up the areas")}
fn main() { assert_eq!(Square(3.0).describe(), "square with area 9.00"); assert_eq!(Circle(1.0).describe(), "circle with area 3.14"); // Triangle never wrote describe(): the default from the trait is used. let t = Triangle { base: 3.0, height: 5.0, }; assert_eq!(t.describe(), "triangle with area 7.50"); // `biggest` takes a slice of ONE type (T). A mixed list needs `dyn`. assert_eq!(biggest(&[Square(1.0), Square(3.0), Square(2.0)]), Some(1)); assert_eq!(biggest(&[Circle(0.5), Circle(2.0)]), Some(1)); assert_eq!(biggest(&[Circle(2.0), Circle(0.5)]), Some(0)); // No items, no biggest. assert_eq!(biggest::<Square>(&[]), None); let mixed: [&dyn Shape; 3] = [&Square(2.0), &Circle(1.0), &t]; let expected = 4.0 + std::f64::consts::PI + 7.5; assert!((total_area(&mixed) - expected).abs() < 1e-9); // The default method works through `dyn` too. assert_eq!(mixed[2].describe(), "triangle with area 7.50"); assert_eq!(total_area(&[]), 0.0); println!("all checks passed");}Open in the Rust Playground ↗Run it first: it stops at todo!() with not yet implemented and a hint, and the compiler warns that the parameter is unused. Both are expected. Replace the todo!() with your code and run it again until it prints all checks passed.
Show a solution
trait Shape { fn area(&self) -> f64; fn name(&self) -> String;
/// Default: "<name> with area <area>", the area with two decimals. fn describe(&self) -> String { format!("{} with area {:.2}", self.name(), self.area()) }}
struct Square(f64);struct Circle(f64);struct Triangle { base: f64, height: f64,}
// Area of a square is side * side; of a circle, PI * radius * radius.// Names are "square" and "circle".impl Shape for Square { fn area(&self) -> f64 { self.0 * self.0 } fn name(&self) -> String { String::from("square") }}
impl Shape for Circle { fn area(&self) -> f64 { std::f64::consts::PI * self.0 * self.0 } fn name(&self) -> String { String::from("circle") }}
// Triangle gives only the two required methods and has no describe().// Its describe() must still work, so the default belongs in the trait.impl Shape for Triangle { fn area(&self) -> f64 { self.base * self.height / 2.0 } fn name(&self) -> String { String::from("triangle") }}
/// The position of the item with the largest area, or None for an empty slice./// (If two tie, either position is fine.)fn biggest<T: Shape>(items: &[T]) -> Option<usize> { items .iter() .enumerate() .max_by(|(_, a), (_, b)| a.area().total_cmp(&b.area())) .map(|(position, _)| position)}
/// The total area of a mixed list: a Square next to a Circle./// `&dyn Shape` is a reference to any type that implements Shape.fn total_area(items: &[&dyn Shape]) -> f64 { items.iter().map(|shape| shape.area()).sum()}
fn main() { assert_eq!(Square(3.0).describe(), "square with area 9.00"); assert_eq!(Circle(1.0).describe(), "circle with area 3.14"); // Triangle never wrote describe(): the default from the trait is used. let t = Triangle { base: 3.0, height: 5.0, }; assert_eq!(t.describe(), "triangle with area 7.50"); // `biggest` takes a slice of ONE type (T). A mixed list needs `dyn`. assert_eq!(biggest(&[Square(1.0), Square(3.0), Square(2.0)]), Some(1)); assert_eq!(biggest(&[Circle(0.5), Circle(2.0)]), Some(1)); assert_eq!(biggest(&[Circle(2.0), Circle(0.5)]), Some(0)); // No items, no biggest. assert_eq!(biggest::<Square>(&[]), None); let mixed: [&dyn Shape; 3] = [&Square(2.0), &Circle(1.0), &t]; let expected = 4.0 + std::f64::consts::PI + 7.5; assert!((total_area(&mixed) - expected).abs() < 1e-9); // The default method works through `dyn` too. assert_eq!(mixed[2].describe(), "triangle with area 7.50"); assert_eq!(total_area(&[]), 0.0); println!("all checks passed");}all checks passed
Display for a list of scores
Print a Vec<u32> as scores: 3, 5, 9 (total 17). First try impl Display for Vec<u32> and read the compiler’s answer, then implement Display for the Scores wrapper instead. An empty list prints scores: none.
use std::fmt;
// Goal: print scores as "scores: 3, 5, 9 (total 17)", or "scores: none" when empty.//// Step 1. Uncomment this block, put any body in it, and run. Read the error// (E0117): Display is not yours and Vec<u32> is not yours.//// impl fmt::Display for Vec<u32> {// fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {// write!(f, "scores")// }// }//// Step 2. Comment it out again and write the impl for the wrapper below,// a type that IS yours.struct Scores(Vec<u32>);
impl fmt::Display for Scores { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { todo!("write! the pieces; join the numbers with \", \"; handle the empty case") }}
fn main() { assert_eq!( Scores(vec![3, 5, 9]).to_string(), "scores: 3, 5, 9 (total 17)" ); assert_eq!(Scores(vec![4]).to_string(), "scores: 4 (total 4)"); // Nothing to join, nothing to total. assert_eq!(Scores(vec![]).to_string(), "scores: none"); // The wrapper still holds a normal Vec. assert_eq!(Scores(vec![1, 2]).0.len(), 2); println!("all checks passed");}Open in the Rust Playground ↗Run it first: it stops at todo!() with not yet implemented and a hint, and the compiler warns that the parameter is unused. Both are expected. Replace the todo!() with your code and run it again until it prints all checks passed.
Show a solution
use std::fmt;
// Goal: print scores as "scores: 3, 5, 9 (total 17)", or "scores: none" when empty.//// Step 1. Uncomment this block, put any body in it, and run. Read the error// (E0117): Display is not yours and Vec<u32> is not yours.//// impl fmt::Display for Vec<u32> {// fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {// write!(f, "scores")// }// }//// Step 2. Comment it out again and write the impl for the wrapper below,// a type that IS yours.struct Scores(Vec<u32>);
impl fmt::Display for Scores { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { if self.0.is_empty() { return write!(f, "scores: none"); } let parts: Vec<String> = self.0.iter().map(|n| n.to_string()).collect(); let total: u32 = self.0.iter().sum(); write!(f, "scores: {} (total {})", parts.join(", "), total) }}
fn main() { assert_eq!( Scores(vec![3, 5, 9]).to_string(), "scores: 3, 5, 9 (total 17)" ); assert_eq!(Scores(vec![4]).to_string(), "scores: 4 (total 4)"); // Nothing to join, nothing to total. assert_eq!(Scores(vec![]).to_string(), "scores: none"); // The wrapper still holds a normal Vec. assert_eq!(Scores(vec![1, 2]).0.len(), 2); println!("all checks passed");}all checks passed
A trait for a type you do not own
Implement Stats for the slice type [i32], then call it on a Vec, an array and a sub-slice (a method call finds the method by borrowing the Vec as a slice). Handle an empty slice, a mean that is not a whole number, and numbers so large that adding them as i32 would overflow.
trait Stats { /// The average, or None when there are no numbers. fn mean(&self) -> Option<f64>; /// The largest minus the smallest, or None when there are no numbers. fn spread(&self) -> Option<i64>;}
// Implement the trait for the slice type [i32], a type you do not own.// Vec<i32> and arrays can then call these methods too.impl Stats for [i32] { fn mean(&self) -> Option<f64> { todo!("add up as i64 so large numbers cannot overflow, then divide") }
fn spread(&self) -> Option<i64> { todo!("max and min return Option; the subtraction must not overflow an i32") }}
fn main() { let v = vec![2, 4, 9]; assert_eq!(v.mean(), Some(5.0)); assert_eq!(v.spread(), Some(7)); // Arrays and sub-slices work too. assert_eq!([10, 20].mean(), Some(15.0)); // A mean that is not a whole number. assert_eq!([1, 2].mean(), Some(1.5)); assert_eq!(v[1..].spread(), Some(5)); // Negative numbers. assert_eq!([-3, 3].spread(), Some(6)); // Large numbers: adding them as i32 would overflow. assert_eq!([i32::MAX, i32::MAX].mean(), Some(i32::MAX as f64)); assert_eq!([i32::MIN, i32::MAX].spread(), Some(4_294_967_295)); // No numbers, no answer. let empty: Vec<i32> = vec![]; assert_eq!(empty.mean(), None); assert_eq!(empty.spread(), None); println!("all checks passed");}Open in the Rust Playground ↗Run it first: it stops at todo!() with not yet implemented and a hint, and the compiler warns that the parameter is unused. Both are expected. Replace the todo!() with your code and run it again until it prints all checks passed.
Show a solution
trait Stats { /// The average, or None when there are no numbers. fn mean(&self) -> Option<f64>; /// The largest minus the smallest, or None when there are no numbers. fn spread(&self) -> Option<i64>;}
// Implement the trait for the slice type [i32], a type you do not own.// Vec<i32> and arrays can then call these methods too.impl Stats for [i32] { fn mean(&self) -> Option<f64> { if self.is_empty() { return None; } let total: i64 = self.iter().map(|&n| n as i64).sum(); Some(total as f64 / self.len() as f64) }
fn spread(&self) -> Option<i64> { let max = *self.iter().max()?; let min = *self.iter().min()?; Some(max as i64 - min as i64) }}
fn main() { let v = vec![2, 4, 9]; assert_eq!(v.mean(), Some(5.0)); assert_eq!(v.spread(), Some(7)); // Arrays and sub-slices work too. assert_eq!([10, 20].mean(), Some(15.0)); // A mean that is not a whole number. assert_eq!([1, 2].mean(), Some(1.5)); assert_eq!(v[1..].spread(), Some(5)); // Negative numbers. assert_eq!([-3, 3].spread(), Some(6)); // Large numbers: adding them as i32 would overflow. assert_eq!([i32::MAX, i32::MAX].mean(), Some(i32::MAX as f64)); assert_eq!([i32::MIN, i32::MAX].spread(), Some(4_294_967_295)); // No numbers, no answer. let empty: Vec<i32> = vec![]; assert_eq!(empty.mean(), None); assert_eq!(empty.spread(), None); println!("all checks passed");}all checks passed
Read more
- The Rust Book, chapter 10.2: Traits: defining, default methods, bounds, blanket impls and the orphan rule.
- The Rust Book, chapter 20.2: Advanced Traits: associated types, fully qualified syntax, supertraits and the newtype pattern.
- Rust by Example: Traits, with pages on disambiguating overlapping traits and supertraits.
- The Reference: Traits and Implementations, where the orphan rules are written out.
- Error pages: E0117 (orphan rule), E0119 (conflicting impls) and E0782 (a trait used as a type).
Learn Rust is an independent project, not affiliated with the Rust Project or the Rust Foundation. Code and text are under the MIT licence.Source on GitHub.