R
RUSTY YELLOW PAGES

implKeyword


Explanation

impl introduces a block of code attached to a type, and means one of two unrelated things depending on whether a trait name follows it:

  1. impl Trait for Type { ... } — implements a trait for a concrete type, providing bodies for the trait's required methods (and optionally overriding its default ones). This is the only place a trait's methods get connected to a specific type.
  2. impl Type { ... } — an inherent impl block, with no trait involved at all. Methods and associated functions defined here belong to the type itself, not to any trait — Vec::new(), Vec::push, and most of a type's everyday API are inherent, defined in a plain impl Vec<T> { ... } block in the standard library's own source.

A type can have any number of impl blocks — inherent ones and trait-implementing ones — spread across a module or even merged for organization; the compiler treats every impl Type { ... } block for the same type as one contiguous set of inherent items, and likewise for each distinct trait implementation.

impl also appears in a third, syntactically unrelated position: impl Trait in argument or return-type position (fn process(item: impl Display), fn make_iter() -> impl Iterator<Item = u32>). This isn't an impl block at all — no braces, no body — it's a type-position shorthand meaning "some concrete type implementing this trait, chosen by the caller (argument position) or the function (return position), but not named." Don't confuse it with impl Trait for Type: same keyword, completely different grammar position and meaning, the same by-position-disambiguation pattern seen with tokens like &.

Usage examples

Inherent impl block alongside a trait impl block

struct Cat;

impl Cat { // <- inherent impl block: no trait involved
    fn new() -> Self { Cat }
}

trait Greet {
    fn greet(&self) -> String;
}

impl Greet for Cat { // <- trait impl block: connects Greet's methods to Cat
    fn greet(&self) -> String { "meow".into() }
}

Runtime polymorphism

A plugin system needs many unrelated types behind one interface — each type gets its own impl Trait for Type block, and callers hold them all as Box<dyn Trait> without caring which concrete type is underneath.

trait Command {
    fn run(&self, input: &str);
}

struct EchoCommand;
impl Command for EchoCommand { // <- trait impl: connects Command's method to EchoCommand
    fn run(&self, input: &str) {
        println!("{input}");
    }
}

struct UppercaseCommand;
impl Command for UppercaseCommand { // <- a second, independent trait impl for a different type
    fn run(&self, input: &str) {
        println!("{}", input.to_uppercase());
    }
}

let commands: Vec<Box<dyn Command>> = vec![Box::new(EchoCommand), Box::new(UppercaseCommand)];
for command in &commands {
    command.run("status: ready");
}

Each impl Command for ... block is independent and can live in its own module or crate, which is exactly the decoupling trait objects & dynamic dispatch relies on — the trait, not a shared base type, is what ties these implementations together.

Writing generic code

A function that only reads its argument through one trait's methods can accept impl Trait in argument position instead of a generic type parameter with a bound — same static dispatch, terser signature, at the cost of the caller being unable to name the type explicitly.

fn log_reading(value: impl std::fmt::Display) { // <- `impl Trait` in argument position: not a block, no braces
    println!("reading: {value}");
}

log_reading(21.5);
log_reading("overheating");

impl Trait in argument position desugars to the same monomorphized generic function as fn log_reading<T: Display>(value: T) — see static dispatch & monomorphization for why this costs nothing at runtime. An explicit generic parameter becomes useful once the signature needs to refer to T more than once (e.g. two parameters that must share the same concrete type).

Explanation

Embedded support: Full

impl means exactly the same thing under #![no_std] as in hosted Rust — the same choice between an inherent block and a trait-implementing block, made the same way. Worth naming explicitly is how central the trait-implementing form is to the embedded ecosystem: a driver crate written against embedded-hal is, at its simplest, one struct plus one or more impl <HalTrait> for MyDriver blocks translating the trait's generic method calls into the specific register writes a real chip needs.

Usage examples

Implementing an embedded-hal trait for a custom driver type

struct MockLed {
    state: bool,
}

impl embedded_hal::digital::OutputPin for MockLed { // <- `impl Trait for Type`: connects OutputPin's methods to MockLed
    type Error = core::convert::Infallible;

    fn set_high(&mut self) -> Result<(), Self::Error> {
        self.state = true;
        Ok(())
    }

    fn set_low(&mut self) -> Result<(), Self::Error> {
        self.state = false;
        Ok(())
    }
}