Trait bounds

Contents
Bound with trait
Where clauses
Combine bounds
Static dispatch

Bound with trait

A bare placeholder T allows no method calls, since the compiler knows nothing about it. A bound promises that T implements one trait, which unlocks that trait methods. Display as a bound means every item prints with empty braces.

fn print_all<T: std::fmt::Display>(items: &[T]) { for item in items { println!("{}", item); } }

Integers and strings both implement Display, so both slide through this function. Types without Display are rejected before the program ever runs. The bound is checked at compile time, not at run time.

Where clauses

Long bound lists crowd the function header and hurt readability. A where clause moves those promises below the parameter list with the same meaning. Short headers stay short while complex requirements stay visible.

fn show<T>(value: T) where T: std::fmt::Display, { println!("{}", value); }

Value prints because the clause guarantees Display. Adding a second bound later means adding one more line, not rewriting the header. Prefer where clauses once bounds grow past a glance.

Combine bounds

One placeholder can promise several traits at once with plus signs between them. Clone plus Display means values duplicate and print. The function below prints a clone beside the original value.

fn dump<T: std::fmt::Display + Clone>(value: T) { println!("{} {}", value.clone(), value); }

The plus sign reads as and, so T must satisfy every listed trait. Missing any one bound is a compile error at the call site. Combined bounds keep flexible functions honest.

Static dispatch

Generic functions compile to a separate copy per concrete type, a strategy called static dispatch. Each copy calls its method directly with no lookup at run time. The total below sums any shape, and every shape gets its own fast path.

trait Shape { fn area(&self) -> f64; } fn total<S: Shape>(shapes: &[S]) -> f64 { shapes.iter().map(|s| s.area()).sum() }

Monomorphization writes one total per shape behind the scenes. Direct calls inline well and branch prediction stays trivial. Dynamic dispatch with trait objects trades that speed for flexibility, and a later lesson covers it.

Next: Closures

Article author: Arthur Isaev

Related articles
Rust
Rust course: zero to hero in 50 lessons
What is Rust and where it runs
Install Rust
cargo new
Your first program
Variables
Numbers
Strings
Booleans
Tuples
Arrays
Vectors
Slices
if and else
match
Loops
Ownership
Borrowing
Lifetimes
Functions
Structs
Enums in Rust
Advanced match in Rust
Option combinators in Rust
Result and the question mark in Rust
Advanced strings in Rust
Vec methods in Rust
HashMaps in Rust
Modules in Rust
Crates and workspaces in Rust
Testing in Rust
Error handling
Traits
Generics
Trait bounds
Closures
Iterators
Smart pointers
Threads
Mutex and Arc
Files
CLI arguments
Environment variables
Diagnostics
Time
Parsing strings
TCP networking
Capstone: wordfreq CLI
Capstone: tiny HTTP server
Modern Rust tour
Hero roadmap: the whole course on one page
Install Rust on Windows 11
Install Rust on Ubuntu
Install Rust on Rocky Linux
Install Rust on macOS
Install Rust on FreeBSD
Rust in Docker

Search this site

Channel @aofeed Chat @aofeedchat

Contacts and cooperation:
I recommend our hosting beget.com
Write to info@aredel.com if you:
1. Want to write an article for our site or translate an article into your native language.
2. Want to place thematically relevant ads on the site.
3. Ads on my site pass maximum censorship. If you see an ad block unsuitable for school-age children, shocking or misleading - please contact us by e-mail
4. Found a mistake, inaccuracy, bug, etc. on the site. ... .......
5. Articles can be shared on social media by clicking a network icon: