A trait is Rust's word for "a named collection of method signatures that any type can opt into."
If you've used Java or C# interfaces, C++ abstract classes with pure virtual methods, Haskell type classes, Swift protocols, or Python's abc/Protocol, you already know the gist of it.
The Rust spelling is:
trait Greet {
fn hello(&self) -> String;
}
struct English;
struct German;
impl Greet for English {
fn hello(&self) -> String { "Hello!".to_string() }
}
impl Greet for German {
fn hello(&self) -> String { "Hallo!".to_string() }
}
English and German have nothing in common structurally, but both "implement Greet."
Anywhere code asks for a Greet, either will do.
You've actually been using traits since the enums chapter.
Every time you wrote #[derive(Debug, PartialEq)] on an enum or struct, you were asking the compiler to write the impl Debug for ... and impl PartialEq for ... blocks for you.
That's all derive is: a macro that emits the obvious implementation so you don't have to type it out.
We'll revisit this in a moment.
The first exercise implements Display, a standard library trait, for a temperature type.
Describable provides a bound for a generic function.
Default methods share behavior between implementations without repetition.
dyn Trait lets one collection hold values of different concrete types.
| Trait | What it gives you | Where you know it from |
|---|---|---|
Debug | {:?} formatting | enums |
Display | {} formatting | the exercises below |
PartialEq, Eq | == and != | enums |
Clone, Copy | .clone() and implicit copies | structs and methods |
Default | T::default() | earlier mentions |
Iterator | for x in iter, all the combinators | iterator pipelines in later exercises |
From, Into | T::from(x) and x.into() conversions | earlier conversions |
None of those are magic.
Each is a regular trait defined in std, with implementations for the built-in types where they make sense.
When you derive one, the compiler writes the implementation.
When the generated behavior isn't what you want, you write the implementation by hand.
// Static dispatch: the compiler generates a specialized copy of
// `print_all` for each `T` you use it with. Zero runtime cost,
// but every `T` in one call must be the same concrete type.
fn print_all<T: Display>(items: &[T]) { /* ... */ }
// Dynamic dispatch: one function, one vtable lookup per call.
// The slice can mix different concrete types that all implement
// `Display`.
fn print_all_dyn(items: &[&dyn Display]) { /* ... */ }
You'll use both forms in the exercises below.
For ownership, Box<dyn Trait> gives an unsized trait object a fixed-size handle.
Display is the trait behind the {} placeholder in println!, format!, and friends.
Implementing it for your struct means values of that struct can be formatted as user-facing text the same way a number or a String can.
The trait lives in std::fmt and looks like this:
pub trait Display {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result;
}
Don't be put off by the signature.
In practice you write a one-liner that delegates to the write! macro, which has the same template syntax as println! but writes into the formatter:
use std::fmt;
struct Pixel(u8, u8, u8);
impl fmt::Display for Pixel {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "#{:02X}{:02X}{:02X}", self.0, self.1, self.2)
}
}
After that, format!("{}", Pixel(255, 0, 0)) produces "#FF0000".
#[derive(Display)]?Because there's no obvious default.
Debug has one (print the type name and fields), but Display is for human-readable output and only you know what that should look like for your type.
So you write it by hand.
The Java/C# parallel is overriding toString(); the Python one is __str__.
Useful from the standard library
std::fmt::Displayis the trait.use std::fmt;and thenimpl fmt::Display for Tis the idiomatic spelling.write!is the formatter-targeted cousin ofprintln!. It returnsstd::fmt::Result, which is exactly what yourfmtmethod needs to return, so a singlewrite!(...)call is usually the whole body.- Format specifiers carry over:
{:.1}rounds a float to one decimal place, soformat!("{:.1}", 21.5_f64)is"21.5". You'll want that for the temperature output.
fmt body is one write! call.write! takes the formatter, then a format string, then the args: write!(f, "{:.1}°C", self.celsius).write! already returns fmt::Result, so its result is your return value.
No semicolon on the last line, or use an explicit return.
use std::fmt;
#[derive(Debug, PartialEq)]
struct Temperature {
celsius: f64,
}
/// Formats the temperature as `"<value>°C"` with one decimal place.
/// Examples:
/// - `Temperature { celsius: 21.5 }` → `"21.5°C"`
/// - `Temperature { celsius: -3.0 }` → `"-3.0°C"`
/// - `Temperature { celsius: 100.0 }` → `"100.0°C"`
impl fmt::Display for Temperature {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:.1}°C", self.celsius)
}
}
#[test]
fn test_display_room_temperature() {
let t = Temperature { celsius: 21.5 };
assert_eq!(format!("{t}"), "21.5°C");
}
#[test]
fn test_display_negative() {
let t = Temperature { celsius: -3.0 };
assert_eq!(format!("{t}"), "-3.0°C");
}
#[test]
fn test_display_boiling() {
let t = Temperature { celsius: 100.0 };
assert_eq!(format!("{t}"), "100.0°C");
}
#[test]
fn test_display_rounds_to_one_decimal() {
// `{:.1}` rounds, it doesn't truncate.
let t = Temperature { celsius: 18.27 };
assert_eq!(format!("{t}"), "18.3°C");
}
Now you're on the other side of the contract.
Instead of impling a trait someone else wrote, you'll write the trait yourself, give two types their own implementation, and then write a generic function that accepts anything implementing it.
trait Describable {
fn describe(&self) -> String;
}
That's the entire interface.
Any type can opt in by writing impl Describable for MyType { fn describe(&self) -> String { ... } }.
Once a trait exists, you can use it as a bound on a generic parameter to say "I accept any T, as long as T implements this trait":
fn print_one<T: Describable>(item: &T) {
println!("{}", item.describe());
}
This is Rust's answer to "polymorphism."
The compiler stamps out one specialized copy of print_one per type you call it with (called monomorphization; the C++ template crowd will feel at home).
There's no runtime dispatch and no boxing.
Real code often spells the same kind of bound in one of these forms:
// Multiple bounds with `+`:
fn show<T: Describable + std::fmt::Debug>(item: &T) { /* ... */ }
// Same thing, written with a `where` clause. Easier to read once you
// have several parameters or long bounds:
fn show<T>(item: &T)
where
T: Describable + std::fmt::Debug,
{
/* ... */
}
// `impl Trait` in argument position is shorthand for a single
// unnamed generic parameter:
fn show(item: &impl Describable) { /* ... */ }
For this exercise, use the simple <T: Describable> form.
You only need to recognize the others as different spellings of the same machinery.
Useful from the standard library
- The Rust Book on traits walks through definitions, implementations, and bounds with more examples than fit here.
Vec<String>::join("\n")(and any&[String].join(...)) is handy for theprint_descriptionsexercise: build aVec<String>of per-item descriptions, then join them with newlines.- The standard
Iterator::mapplus.collect::<Vec<_>>()is the idiomatic way to turn a&[T]into aVec<String>.Iterator::mapandcollectappear throughout the iterator exercises.
describe methods are one-line format! calls:
format!("{} by {}", self.title, self.author) for Book.format!("{} ({})", self.title, self.year) for Movie.print_descriptions, build a Vec<String> and join it:
let lines: Vec<String> = items.iter().map(|x| x.describe()).collect();
lines.join("\n")
.map/.collect yet (the iterators chapter covers them properly), a plain for loop works just as well:
let mut lines: Vec<String> = Vec::new();
for item in items {
lines.push(item.describe());
}
lines.join("\n")
/// A type that knows how to describe itself in one short line.
trait Describable {
fn describe(&self) -> String;
}
#[derive(Debug)]
struct Book {
title: String,
author: String,
}
#[derive(Debug)]
struct Movie {
title: String,
year: u16,
}
/// Implement `Describable` for `Book` so that
/// `Book { title: "Dune".into(), author: "Herbert".into() }.describe()`
/// returns `"Dune by Herbert"`.
impl Describable for Book {
fn describe(&self) -> String {
format!("{} by {}", self.title, self.author)
}
}
/// Implement `Describable` for `Movie` so that
/// `Movie { title: "Arrival".into(), year: 2016 }.describe()`
/// returns `"Arrival (2016)"`.
impl Describable for Movie {
fn describe(&self) -> String {
format!("{} ({})", self.title, self.year)
}
}
/// Generic function with a trait bound.
///
/// Accepts a slice of *any* type `T` that implements `Describable`,
/// calls `.describe()` on each element, and joins the results with a
/// newline (`"\n"`) between them. An empty slice returns `""`.
///
/// Note: because of the `T: Describable` bound, every element of one
/// call must be the same concrete type. Mixing `Book`s and `Movie`s
/// in the same call needs trait objects, which is the next step.
fn print_descriptions<T: Describable>(items: &[T]) -> String {
let mut lines = Vec::new();
for item in items {
lines.push(item.describe());
}
lines.join("\n")
}
#[test]
fn test_book_describe() {
let b = Book {
title: "Dune".to_string(),
author: "Herbert".to_string(),
};
assert_eq!(b.describe(), "Dune by Herbert");
}
#[test]
fn test_movie_describe() {
let m = Movie {
title: "Arrival".to_string(),
year: 2016,
};
assert_eq!(m.describe(), "Arrival (2016)");
}
#[test]
fn test_print_descriptions_books() {
let library = [
Book {
title: "Dune".to_string(),
author: "Herbert".to_string(),
},
Book {
title: "Hyperion".to_string(),
author: "Simmons".to_string(),
},
];
assert_eq!(
print_descriptions(&library),
"Dune by Herbert\nHyperion by Simmons"
);
}
#[test]
fn test_print_descriptions_movies() {
let films = [Movie {
title: "Arrival".to_string(),
year: 2016,
}];
assert_eq!(print_descriptions(&films), "Arrival (2016)");
}
#[test]
fn test_print_descriptions_empty() {
let films: [Movie; 0] = [];
assert_eq!(print_descriptions(&films), "");
}
A trait method can ship with a default body. Implementors get that method for free, but any one of them can override it if the default doesn't fit.
trait Greet {
fn name(&self) -> &str;
// Default body. Implementors get this for free.
fn hello(&self) -> String {
format!("Hello, {}!", self.name())
}
}
A type that says impl Greet for X { fn name(&self) -> &str { "world" } } automatically has .hello() available, returning "Hello, world!", without writing it out.
Provide a fn hello in the impl block and yours wins instead.
This is how Iterator gets away with offering dozens of methods (map, filter, sum, count, ...) while only requiring you to implement one: fn next(&mut self) -> Option<Self::Item>.
Every other method is a default body written in terms of next.
Haskellers will recognise the pattern from type class default methods; Java added the same feature as "default methods on interfaces" in Java 8.
The Logger trait has one required method, log, which formats a single line.
Its warn and error methods provide default bodies built on top of log.
trait Logger {
fn log(&self, msg: &str) -> String;
fn warn(&self, msg: &str) -> String {
self.log(&format!("[WARN] {msg}"))
}
fn error(&self, msg: &str) -> String {
self.log(&format!("[ERROR] {msg}"))
}
}
The shape should look familiar from logging APIs in other languages.
Because warn and error live on the trait, each new implementor gets both without writing them again.
You'll implement that contrast with two types:
PlainLogger returns the message untouched.
It uses both defaults as written, so all you have to write is log.TaggedLogger { tag: String } prepends a tag (e.g. "auth: ...").
It uses the default warn, but overrides error to swap the [ERROR] prefix for a louder [CRITICAL] prefix.That asymmetry lets each implementor keep the defaults that fit and replace only the behavior that differs.
Useful from the standard library
- The
format!macro is the workhorse here. Defaultwarnbuilds"[WARN] {msg}"and hands it back toself.log, so whatever decorationlogdoes (the tag, inTaggedLogger's case) wraps the warning prefix.- Default methods are written inside the
traitblock, with a body instead of a trailing semicolon. A method ending in a semicolon remains required, while a method with a body can be inherited or overridden.
PlainLogger::log is one line: msg.to_string().
That's all.
Do not write warn or error for PlainLogger; the defaults already do the right thing.TaggedLogger::log is also one line: format!("{}: {}", self.tag, msg).
The default warn calls this log, so warn("slow") ends up as "auth: [WARN] slow" automatically.TaggedLogger::error is the override.
The pattern mirrors the default body, just with a different prefix:
fn error(&self, msg: &str) -> String {
self.log(&format!("[CRITICAL] {msg}"))
}
PlainLogger inherits both defaults, TaggedLogger inherits one and overrides the other.
The trait is the only place a default ever lives, so there's no surprise about where behavior comes from.
/// A small `Logger` trait. One required method (`log`), plus two
/// default methods that build on it (`warn` and `error`).
///
/// Implementors only have to write `log`; they get `warn` and
/// `error` for free unless they explicitly override them.
trait Logger {
/// Required: turn a message into the final log line.
fn log(&self, msg: &str) -> String;
/// Default: prepend `"[WARN] "` and forward through `log`.
fn warn(&self, msg: &str) -> String {
self.log(&format!("[WARN] {msg}"))
}
/// Default: prepend `"[ERROR] "` and forward through `log`.
fn error(&self, msg: &str) -> String {
self.log(&format!("[ERROR] {msg}"))
}
}
/// A logger that returns the message untouched. It should use the
/// default `warn` and `error` (do *not* write them in this impl).
struct PlainLogger;
impl Logger for PlainLogger {
/// Return `msg` as a `String`, with nothing added.
fn log(&self, msg: &str) -> String {
msg.to_string()
}
}
/// A logger that prepends a tag, like `"auth: something went wrong"`.
///
/// It uses the default `warn` (so warnings come out as
/// `"auth: [WARN] ..."`), but *overrides* `error` to use a louder
/// `[CRITICAL]` prefix instead of the default `[ERROR]`.
struct TaggedLogger {
tag: String,
}
impl Logger for TaggedLogger {
/// Return `"{tag}: {msg}"`.
fn log(&self, msg: &str) -> String {
format!("{}: {}", self.tag, msg)
}
/// Override: build a `[CRITICAL]`-prefixed message and forward
/// it through `log` (so the tag still wraps the result).
/// Expected output for `TaggedLogger { tag: "auth" }.error("nope")`
/// is `"auth: [CRITICAL] nope"`.
fn error(&self, msg: &str) -> String {
self.log(&format!("[CRITICAL] {msg}"))
}
}
#[test]
fn plain_logger_log_is_passthrough() {
let l = PlainLogger;
assert_eq!(l.log("ready"), "ready");
}
#[test]
fn plain_logger_inherits_warn() {
let l = PlainLogger;
assert_eq!(l.warn("slow query"), "[WARN] slow query");
}
#[test]
fn plain_logger_inherits_error() {
let l = PlainLogger;
assert_eq!(l.error("disk full"), "[ERROR] disk full");
}
#[test]
fn tagged_logger_prepends_tag() {
let l = TaggedLogger {
tag: "auth".to_string(),
};
assert_eq!(l.log("ok"), "auth: ok");
}
#[test]
fn tagged_logger_inherits_warn_via_its_own_log() {
// Default warn calls self.log, so the tag wraps the [WARN] prefix.
let l = TaggedLogger {
tag: "auth".to_string(),
};
assert_eq!(l.warn("slow"), "auth: [WARN] slow");
}
#[test]
fn tagged_logger_overrides_error_with_critical() {
let l = TaggedLogger {
tag: "auth".to_string(),
};
assert_eq!(l.error("nope"), "auth: [CRITICAL] nope");
}
The generic print_descriptions<T: Describable> from step 3 is fast and zero-cost, but it has one limit: every element of a single call must be the same concrete T.
You can pass &[Book] or &[Movie], but not a slice that contains both.
That's because the compiler picks one T per call site and produces a specialized copy of the function for it.
The slice type &[T] has to agree on a single element type, and two different structs are two different types as far as the type system is concerned.
dyn TraitWhen you want a single collection that holds different concrete types as long as they all implement the same trait, you reach for a trait object, spelled dyn Trait:
fn run_all(items: &[&dyn Validator], input: &str) {
for v in items {
let _ = v.check(input);
}
}
&dyn Validator is a fat pointer: two words that point to the value and to a vtable of function pointers, one for each trait method.
At each call to .check(...), Rust looks up the function in that vtable.
C++ folks will recognize the same machinery as virtual methods, but here you opt into it at the call site instead of on the class.
run_all is also compiled exactly once rather than once per concrete type.
That trades one vtable lookup per call for the ability to mix concrete types in the slice.
fn f<T: Trait>(x: &T) | fn f(x: &dyn Trait) | |
|---|---|---|
| Dispatch | static, decided at compile time | dynamic, vtable lookup at runtime |
| Code size | one copy per T you use | one copy total |
| Mixed collections | no | yes |
| Runtime cost | none | one indirect call per trait-method call |
Neither is "better."
Use generics by default for performance and flexibility, and reach for dyn Trait when you genuinely need heterogeneous storage or want a smaller binary.
You'll use trait objects to build a small validation library. Each validator needs one method:
trait Validator {
/// `Ok(())` on success, `Err(message)` on failure.
fn check(&self, input: &str) -> Result<(), String>;
}
You'll give three structs one rule each:
MinLength { n }: input must have at least n characters.MustContain { needle }: input must contain the given substring.MustNotContain { forbidden }: input must not contain the given substring.MinLength, MustContain, and MustNotContain are different types, but one &[&dyn Validator] slice can hold all three.
Each rule can carry its own configuration while the call site only needs a list of values that can validate.
If you take the optional password validator later, you'll use the same idea for a configurable set of checks.
Box<dyn Trait>You'll also see Box<dyn Trait> in Rust code:
let rules: Vec<Box<dyn Validator>> = vec![
Box::new(MinLength { n: 8 }),
Box::new(MustContain { needle: "@".to_string() }),
];
The reason: dyn Trait has no statically known size (the three implementors above can carry different fields, so they don't all take up the same number of bytes), so the compiler won't let you put bare dyn Validator values directly in a Vec.
A Box is a heap allocation with a fixed-size pointer that lives on the stack, which sidesteps the size problem.
Box<dyn Trait> is the owning form of this fixed-size handle.
&dyn Validator borrows through a fixed-size handle instead of taking ownership.
Useful from the standard library
- The Rust Book on trait objects.
str::contains(with a&strargument) is all you need for theMustContain/MustNotContainchecks.- Inside
collect_errors, a plainforloop pushing into aVec<String>is the most direct form. An.iter().filter_map(...)chain expresses the same loop with iterator adapters.
check impls follow MinLength exactly.
Use input.contains(&self.needle) and the inverse:
if !input.contains(&self.needle) {
Err(format!("must contain '{}'", self.needle))
} else {
Ok(())
}
and:
if input.contains(&self.forbidden) {
Err(format!("must not contain '{}'", self.forbidden))
} else {
Ok(())
}
collect_errors, a plain for loop is the most readable:
let mut errors = Vec::new();
for v in validators {
if let Err(msg) = v.check(input) {
errors.push(msg);
}
}
errors
&dyn Validator means each v in the loop is a &&dyn Validator.
Method calls auto-deref, so v.check(input) works without ceremony.validators.iter().filter_map(|v| v.check(input).err()).collect().
/// A composable validation rule. `Ok(())` means the input is fine
/// for this rule; `Err(message)` explains what's wrong.
trait Validator {
fn check(&self, input: &str) -> Result<(), String>;
}
/// Rule: the input must be at least `n` characters long.
///
/// Already implemented for you as the worked example. Read it,
/// then write the other two impls in the same way.
struct MinLength {
n: usize,
}
impl Validator for MinLength {
fn check(&self, input: &str) -> Result<(), String> {
if input.chars().count() < self.n {
Err(format!("must be at least {} characters", self.n))
} else {
Ok(())
}
}
}
/// Rule: the input must contain `needle` as a substring.
///
/// On failure return `Err(format!("must contain '{}'", self.needle))`.
struct MustContain {
needle: String,
}
impl Validator for MustContain {
fn check(&self, input: &str) -> Result<(), String> {
if input.contains(&self.needle) {
Ok(())
} else {
Err(format!("must contain '{}'", self.needle))
}
}
}
/// Rule: the input must *not* contain `forbidden` as a substring.
///
/// On failure return `Err(format!("must not contain '{}'", self.forbidden))`.
struct MustNotContain {
forbidden: String,
}
impl Validator for MustNotContain {
fn check(&self, input: &str) -> Result<(), String> {
if input.contains(&self.forbidden) {
Err(format!("must not contain '{}'", self.forbidden))
} else {
Ok(())
}
}
}
/// Run every validator against `input` and collect the failure
/// messages in the order the validators appear.
///
/// The slice element type is `&dyn Validator`: a reference to a
/// trait object. The slice can mix `MinLength`, `MustContain`, and
/// `MustNotContain` (and any future implementor) freely. That's the
/// whole point of trait objects.
///
/// An input that passes everything returns an empty `Vec`. The
/// returned `Vec<String>` contains only the `Err` messages.
fn collect_errors(validators: &[&dyn Validator], input: &str) -> Vec<String> {
let mut errors = Vec::new();
for validator in validators {
if let Err(message) = validator.check(input) {
errors.push(message);
}
}
errors
}
#[test]
fn min_length_passes_when_long_enough() {
let v = MinLength { n: 3 };
assert_eq!(v.check("abcd"), Ok(()));
}
#[test]
fn min_length_fails_when_too_short() {
let v = MinLength { n: 5 };
assert_eq!(
v.check("hi"),
Err("must be at least 5 characters".to_string())
);
}
#[test]
fn must_contain_passes() {
let v = MustContain {
needle: "@".to_string(),
};
assert_eq!(v.check("alice@example.com"), Ok(()));
}
#[test]
fn must_contain_fails() {
let v = MustContain {
needle: "@".to_string(),
};
assert_eq!(v.check("alice"), Err("must contain '@'".to_string()));
}
#[test]
fn must_not_contain_passes() {
let v = MustNotContain {
forbidden: " ".to_string(),
};
assert_eq!(v.check("no-spaces"), Ok(()));
}
#[test]
fn must_not_contain_fails() {
let v = MustNotContain {
forbidden: " ".to_string(),
};
assert_eq!(
v.check("has a space"),
Err("must not contain ' '".to_string())
);
}
#[test]
fn collect_errors_on_clean_input() {
let r1 = MinLength { n: 3 };
let r2 = MustContain {
needle: "@".to_string(),
};
let rules: Vec<&dyn Validator> = vec![&r1, &r2];
assert!(collect_errors(&rules, "a@b").is_empty());
}
#[test]
fn collect_errors_reports_all_failures_in_order() {
// Three different concrete types, one slice. That's the trait
// object payoff: pluggable rules, none of which know about each
// other.
let r1 = MinLength { n: 8 };
let r2 = MustContain {
needle: "@".to_string(),
};
let r3 = MustNotContain {
forbidden: " ".to_string(),
};
let rules: Vec<&dyn Validator> = vec![&r1, &r2, &r3];
// Input must trip all three rules:
// - shorter than 8 chars (fails MinLength)
// - no '@' anywhere (fails MustContain)
// - contains a space (fails MustNotContain)
assert_eq!(
collect_errors(&rules, "a b"),
vec![
"must be at least 8 characters".to_string(),
"must contain '@'".to_string(),
"must not contain ' '".to_string(),
]
);
}
#[test]
fn collect_errors_with_no_rules_returns_empty() {
let rules: Vec<&dyn Validator> = vec![];
assert!(collect_errors(&rules, "anything").is_empty());
}
You implemented the standard library's Display trait and defined a Describable trait of your own.
You used Describable as a generic bound, then shared behavior through default methods.
Finally, you switched from a generic to a trait object so one slice could hold several kinds of validation rule.
What we learned
- A trait is a named collection of method signatures. Any type can opt in with
impl TraitName for TypeName { ... }. Same idea as Java/C# interfaces, Haskell type classes, Swift protocols, or C++ abstract classes with pure virtual methods.#[derive(...)]is sugar: the compiler writes the obviousimpl Trait for Typeblock for you.Debug,PartialEq,Eq,Clone,Copy,Default,Hash, andOrdare the everyday derivable ones.Displayis not derivable because there's no one-size-fits-all human-readable format.impl Displayis thetoString/__str__of Rust. Implementfn fmt(&self, f: &mut Formatter<'_>) -> fmt::Resultwith a singlewrite!(...)call.- Trait bounds on a generic say "I accept any
Tthat implements this trait."fn f<T: Trait>(x: &T)is the basic form,T: A + Bcombines bounds, andwhereclauses let you push long bounds out of the signature.- Default methods in the trait body give every implementor a baseline behavior. Override per type when you need to.
- Generics dispatch statically, so the compiler creates one specialized copy of the function per concrete
T. Use them when each call only needs one concrete type.- Trait objects (
dyn Trait) dispatch dynamically through a vtable. Use them when one slice orVecneeds to hold several concrete types at once.Box<dyn Trait>solves the "trait objects have no known size" problem so they can live in owning containers likeVec. Related ownership patterns withBox,Rc, andRefCellappear in the optional smart pointers material.- Many standard library traits you already use (
Iterator,From,Into,PartialEq, ...) follow the same rules. You can define traits of your own or implement standard traits for your own types.