Start learning free

LESSON 408 OF 414 · OBJECT-ORIENTED PROGRAMMING

Interfaces in Rust: Traits and Trait Objects

Lesson Read the full explanation

TL ; DR:

An interface is a pure contract — a list of methods that a type promises to implement, with no shared data and no shared code. Unlike inheritance ("is a"), interfaces express "can do".


// Rust uses traits as interfaces:
trait Speaker {
    fn speak(&self);
}
struct Dog;
impl Speaker for Dog {
    fn speak(&self) { println!("Woof"); }
}


Full lesson:

An interface defines what a type can do, not how it does it or what it is. A Truck, a Robot, and a Drone aren't the same kind of thing — but they can all go(). In Rust you'd put go() in a Movable trait and have each one implement it.

Interface vs abstract class

Abstract classInterface
Has fieldsYesUsually no
Has method bodiesYes (some)No (or default impls only)
Multiple "inheritance"Usually noYes — a type can implement many interfaces
Models"is a""can do"

In class-based languages a class inherits from at most one parent class but can implement many interfaces. Rust has no classes or inheritance at all: a struct can implement as many traits as you like. That's why interfaces are the right tool for cross-cutting capabilities like Clone, Debug, Iterator, etc.

The language landscape

  • Rust — no interface keyword; a trait is the interface, and a type opts in with impl Trait for Type.
  • C# / Java — first-class interface keyword. C# uses : for both inheritance and implementation; Java uses extends for inheritance and implements for interfaces.
  • Python — no separate interface; abstract base classes (ABCs) with all-abstract methods serve the same role.
  • JavaScript — no interface keyword in plain JS (TypeScript has it). The convention is a stub class.
  • C++ — a class with only pure-virtual methods is an interface. Multiple inheritance lets a class implement several.

Your task

Declare a Speaker trait with a speak method, then create a Dog struct that implements it, with a speak() method that prints "Woof". In main_function, call it through a Box<dyn Speaker>.

Expected output:

Woof
STEP 1 OF 6
    Ctrl + Enter to check

    RUST · OBJECT-ORIENTED PROGRAMMING

    Interfaces in Rust, explained

    Interfaces and traits

    An interface is a pure contract: a list of methods that a type promises to provide, with no data attached. It describes what a type can do, not what it is. A Truck, a Robot and a Drone are different kinds of thing, but all of them can move, so a "movable" contract fits them all.

    Rust has no interface keyword. Its interfaces are traits. A trait lists method signatures, and a type opts in with a separate block, impl TraitName for TypeName. Three things set traits apart:

    • Data, contract and behaviour are separate. The struct holds fields, the trait states the contract, and the impl block supplies the behaviour.
    • A type can implement many traits. Nothing limits how many impl Trait for Type blocks one type has.
    • Implementing is explicit. A type does not satisfy a trait just because it has methods with matching names. If Truck has no impl Movable for Truck, passing it where a Movable is required fails with error[E0277]: the trait bound `Truck: Movable` is not satisfied.

    Step by step

    The lesson builds the whole program from scratch.

    1. Define the trait Speaker with one method, fn speak(&self);. It has no body, so every implementor must provide one.
    2. Declare struct Dog;, a unit struct with no fields. All the dog has to offer is behaviour.
    3. Write impl Speaker for Dog. The trait comes first and the type second. The block must define every method the trait declares, or the code does not compile.
    4. Define speak inside it with the same signature as in the trait, taking &self, and print Woof with the println! macro. The &self borrows the dog read-only.
    5. Add main_function, a function with no parameters that uses the contract.
    6. Store the dog as a trait object. Declare rex with the type Box<dyn Speaker>, create it with Box::new(Dog), and call rex.speak(). The variable's type says only "something that can speak", and the real implementation is chosen at run time.

    The solution in Rust

    trait Speaker {
        fn speak(&self);
    }
    
    struct Dog;
    
    impl Speaker for Dog {
        fn speak(&self) {
            println!("Woof");
        }
    }
    
    fn main_function() {
        let rex: Box<dyn Speaker> = Box::new(Dog);
        rex.speak();
    }
    

    The output is:

    Woof
    

    dyn Speaker has no fixed size, because any type may implement the trait, so it lives behind a pointer. Box provides that pointer by putting the value on the heap. Another type implementing Speaker could replace Dog in that line, and the call below it would not change.

    Two ways to use a trait

    The trait object above uses dynamic dispatch. The other way is a generic bound, where the compiler builds a specialised copy of the function for each concrete type:

    trait Movable { fn go(&self); }
    trait Speaker { fn speak(&self); }
    
    struct Robot;
    struct Truck;
    
    impl Movable for Robot { fn go(&self) { println!("Robot rolls"); } }
    impl Speaker for Robot { fn speak(&self) { println!("Beep"); } }
    impl Movable for Truck { fn go(&self) { println!("Truck drives"); } }
    
    fn start(m: &impl Movable) { m.go(); }
    
    fn both<T: Movable + Speaker>(t: &T) {
        t.go();
        t.speak();
    }
    
    fn main() {
        start(&Truck);
        both(&Robot);
        let fleet: Vec<Box<dyn Movable>> = vec![Box::new(Robot), Box::new(Truck)];
        for m in &fleet {
            m.go();
        }
    }
    

    It prints:

    Truck drives
    Robot rolls
    Beep
    Robot rolls
    Truck drives
    

    Robot implements two traits, and both requires both with Movable + Speaker. Truck implements only Movable, so both(&Truck) would not compile. The fleet vector mixes types behind Box<dyn Movable>.

    Choosing between them

    • Use a generic bound or impl Trait when the function works with one concrete type per call. There is no run-time lookup.
    • Use dyn Trait when one variable or collection must hold different types, as in fleet.

    Interface or abstract class

    Abstract classRust trait
    Holds fieldsYesNo
    Method bodiesSomeOptional defaults
    Several at onceUsually one parentAny number per type
    Models"is a""can do"

    Common mistakes

    • Writing the method in a plain impl Dog block. It then belongs to Dog but does not satisfy Speaker.
    • Leaving out a required method. The compiler reports that not all trait items are implemented.
    • Using a bare trait as a type. In edition 2021 a type must be written Box<dyn Speaker> or &dyn Speaker; Speaker alone is an error.
    • Forgetting Box::new. A Dog value does not convert to Box<dyn Speaker> by itself.

    Key takeaways

    • Traits are Rust's interfaces: a contract of method signatures, implemented explicitly per type.
    • A type can implement any number of traits.
    • Generic bounds give static dispatch, and Box<dyn Trait> gives dynamic dispatch over mixed types.
    Keep going with RustAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free