🎓 A1Learner
🔍

Rust Conditional & Blanket Impls

A conditional impl is an impl block with bounds. Its methods exist only for types that meet the bounds.

The pain: one impl per type

Without a blanket impl, every type needs its own copy of the same body:

trait Shout { fn shout(&self) -> String; }
impl Shout for i32 { fn shout(&self) -> String { format!("{}!", self) } }
impl Shout for String { fn shout(&self) -> String { format!("{}!", self) } }
fn main() {
    println!("{}", 42.shout());
    println!("{}", String::from("hi").shout());
}

A door rule on an impl

The bounds on the impl are the rule — the methods inside exist only for a T that meets them:

impl<T: Display + PartialOrd> Pair<T> {
    fn show_larger(&self) { /* compares and prints */ }
}

Inside, the bounds are promises kept

Because the rule demanded Display + PartialOrd, the method may compare with >= and print with {} freely:

if self.x >= self.y {
    println!("larger is x = {}", self.x);
}

Outside the rule, the method is missing

A Vec has no Display, so Pair<Vec<i32>> never gets show_larger — calling it is refused:

let pair = Pair { x: vec![1], y: vec![2] };
pair.show_larger(); // error E0599: no method named `show_larger`

The library’s own pattern: ToString

std blanket-implements ToString for every Display type. Implement Display by hand — never ToString — and to_string() arrives free.

let text: String = Metre { value: 3.0 }.to_string();
println!("{}", text);

The clash: blanket meets specific

Once a blanket impl covers a type, a hand-written impl for that same type is refused. The error is E0119: conflicting implementations. (This was checked with the compiler.)

impl<T: Display> Loud for T { /* ... */ }

impl Loud for i32 { /* refused — i32 is already covered */ }

Example

use std::fmt::Display;
struct Pair<T> { x: T, y: T }
impl<T: Display + PartialOrd> Pair<T> {
    fn show_larger(&self) {
        if self.x >= self.y { println!("larger is x = {}", self.x); }
        else { println!("larger is y = {}", self.y); }
    }
}
trait Shout { fn shout(&self) -> String; }
impl<T: Display> Shout for T {
    fn shout(&self) -> String { format!("{}!", self) }
}
fn main() {
    let pair = Pair { x: 3, y: 9 };
    pair.show_larger();
    println!("{}", 42.shout());
    println!("{}", "hi".shout());
}

Key points

  • A blanket impl is forever.
  • Publish impl<T: Display> Loud for T once, and the rule is fixed.
  • No one may ever add a special Loud impl for one Display type.
  • Blanket impls are why coherence matters.
  • The orphan rule plus the overlap ban let std make a promise.
  • Display always brings ToString with it.
  • And no rival impl exists anywhere.
  • A blanket impl is a conditional impl that gives a trait to every type that meets the bound, all at once.
  • A conditional impl provides methods only when its bounds hold.

In this example, Pair holds 3 and 9. i32 can be compared and printed. So show_larger exists. 3 is not at least 9. So it prints larger is y = 9. Below, Shout is given by one blanket impl to every Display type. So the number 42 can shout as 42!, and the text hi can shout as hi! — neither type needed its own impl block.

Note

Blanket impls are powerful and can clash.

If a type already has the trait another way, a new blanket impl over it will not compile.

Try it Yourself

Edit the code, then press Run.

Running is simulated · Rust Playground

Exercise

What is a blanket implementation?