The Error Hits
It's late. You wrote a clean generic function, cargo compiles half the crate, then drops this:
error[E0277]: the trait bound `T: std::fmt::Display` is not satisfied
--> src/main.rs:4:20
|
4 | println!("{}", value);
| ^^^^^ `T` cannot be formatted with the default formatter
|
= help: the trait `std::fmt::Display` is not implemented for `T`
= note: required by a bound in `core::fmt::Display`
help: consider restricting type parameter `T`
|
1 | fn print_value(value: T) {
| ++++++++++++++++++++
The compiler is being unusually helpful here โ it even shows the exact fix. You just need to apply it.
What Actually Happened
You wrote a generic function over type T and tried to use a capability โ formatting, cloning, comparing โ that only exists if T implements a specific trait. Rust can't verify that at compile time. So it refuses.
Common triggers, roughly in order of how often you'll hit them:
- Using
println!("{}", value)โ requiresDisplay - Using
println!("{:?}", value)ordbg!()โ requiresDebug - Calling
.clone()โ requiresClone - Using
==or!=โ requiresPartialEq - Using
<,>for sorting โ requiresPartialOrd
Quick Fix: Add the Trait Bound
The compiler's help: line already tells you what to add. Put the trait bound directly on your type parameter.
Before (broken):
fn print_value<T>(value: T) {
println!("{}", value); // E0277: T doesn't implement Display
}
After (fixed):
fn print_value<T: std::fmt::Display>(value: T) {
println!("{}", value); // compiles fine
}
A use import at the top keeps the signature readable:
use std::fmt::Display;
fn print_value<T: Display>(value: T) {
println!("{}", value);
}
Multiple Bounds: Use +
When T needs to satisfy more than one trait, chain them with +:
use std::fmt::{Debug, Display};
fn log_value<T: Display + Debug + Clone>(value: T) {
let copy = value.clone();
println!("display: {}", value);
println!("debug: {:?}", copy);
}
Where Clauses: When Signatures Get Long
Stack multiple bounds on multiple type parameters and your function signature becomes a wall of text. Move them to a where clause instead:
// Hard to read:
fn compare_and_show<T: PartialOrd + Display, U: Debug + Clone>(a: T, b: U) { ... }
// Much cleaner with where:
fn compare_and_show<T, U>(a: T, b: U)
where
T: PartialOrd + Display,
U: Debug + Clone,
{
if a > b { println!("{}", a); }
println!("{:?}", b.clone());
}
where clauses also let you express things you simply can't write inline โ bounds on associated types, or constructions like Vec<T>: Display.
Permanent Fix: Derive Common Traits on Your Types
If you own the struct or enum being passed in, #[derive] is the cleanest path. Your type satisfies the bounds automatically, no manual implementation needed:
#[derive(Debug, Clone, PartialEq, PartialOrd)]
struct Score {
player: String,
value: u32,
}
fn print_score<T: Debug + Clone>(item: T) {
let copy = item.clone();
println!("{:?}", copy);
}
fn main() {
let s = Score { player: "Alice".into(), value: 42 };
print_score(s); // works โ Score derives Debug + Clone
}
Traits you can auto-derive:
Debugโ{:?}formattingCloneandCopyโ value duplicationPartialEqandEqโ equality checksPartialOrdandOrdโ ordering and sortingHashโ use as HashMap keyDefaultโ zero or empty value
Display is the exception โ it must be implemented manually. There's no single sensible default for custom output formatting.
Struct and Impl Blocks Need Bounds Too
E0277 isn't limited to standalone functions. Generic structs with impl blocks need the bound declared on the impl itself:
struct Wrapper<T> {
inner: T,
}
// Wrong: no bound, but we use Display inside
impl<T> Wrapper<T> {
fn show(&self) {
println!("{}", self.inner); // E0277
}
}
// Correct: bound on the impl block
impl<T: std::fmt::Display> Wrapper<T> {
fn show(&self) {
println!("{}", self.inner);
}
}
You can also split your impl blocks to keep constraints narrow. Put methods that need the bound in a separate impl<T: Bound> block. Leave the rest in a plain impl<T>. It makes the constraint explicit and easy to audit.
When You Can't Add a Bound: Trait Objects
Sometimes you don't control the type parameter โ third-party types, mixed collections, plugin-style APIs. Reach for a trait object (dyn Trait) instead of a bound:
fn print_any(value: &dyn std::fmt::Display) {
println!("{}", value);
}
fn main() {
print_any(&42);
print_any(&"hello");
print_any(&3.14);
}
Trait objects use dynamic dispatch โ a small runtime cost per call, versus zero cost for monomorphized generics. They also don't work with non-object-safe traits. But when generics aren't an option, they get the job done.
Verify the Fix
Confirm E0277 is gone:
cargo build 2>&1 | grep E0277
# Should return nothing
cargo build
# Should output: Compiling ... / Finished ...
For added confidence, drop a quick test to verify the bound holds at the call site with your actual types:
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_print_value() {
print_value(42); // i32 implements Display
print_value("hello"); // &str implements Display
}
}
cargo test
# running 1 test
# test tests::test_print_value ... ok
Cheat Sheet
- Inline bound:
fn foo<T: Trait>(x: T)โ simple, one or two traits - Where clause:
fn foo<T>(x: T) where T: Traitโ multiple bounds or complex types - Derive:
#[derive(Debug, Clone)]on your struct โ when you own the type - Trait object:
&dyn TraitorBox<dyn Trait>โ when generics aren't an option

