Start learning free

LESSON 404 OF 414 · OBJECT-ORIENTED PROGRAMMING

Method Overriding in Rust: Own Methods and Trait Defaults

Lesson Read the full explanation

TL ; DR:

In languages with class inheritance, a child class that defines a method with the same name as the parent's overrides it. Rust has no inheritance, so the idea shows up in two ways: a type defining its own method with the same name, or a type replacing a trait's default method. Different types can give the same method name different behavior.


trait Animal {
    fn bark(&self) { println!("Woof!"); }
}
struct Dog;
impl Animal for Dog {}
struct Puppy;
impl Animal for Puppy {
    fn bark(&self) { println!("Yip!"); }
}


Full lesson:

You've seen that a Puppy can reuse a Dog's behavior by holding one and forwarding calls to it. Overriding is what happens when the puppy wants to change that behavior instead of forwarding it.

Why override?

A Puppy is a special kind of Dog, but it sounds different — "Yip!" instead of "Woof!". Instead of inventing a new method name, you define bark() on the Puppy too. Callers just call bark() and the version belonging to the value's own type runs.

How it works in Rust

  • No keywords — there is no virtual, override or @Override. Puppy simply has its own impl method named bark, and Dog's method is a different function on a different type.
  • Trait default methods — a trait can give a method a default body, and a type that implements the trait can replace it by writing its own fn in its impl Trait for Type block (the TL;DR example above).

Your task

The Dog struct already has a bark method. Inside the impl Puppy block, add a bark method of its own so it prints "Yip! I am " followed by the puppy's name. The code in main_function creates a Puppy named "Buddy" and calls bark() on it.

Expected output:

Yip! I am Buddy
STEP 1 OF 2
    Ctrl + Enter to check

    RUST · OBJECT-ORIENTED PROGRAMMING

    Method Overriding in Rust, explained

    What overriding means

    Overriding is replacing a method's behaviour for a more specific type. A Puppy is a kind of Dog, but it should say "Yip!" where a dog says "Woof!". Callers should be able to write bark() and get the right sound without checking which kind of animal they hold.

    Rust has no class inheritance, so there is no parent method to replace and no override keyword. The idea shows up in two forms:

    • A type defines its own method with the same name. Each type has its own namespace of methods, so Puppy::bark and Dog::bark never collide. This is the form the lesson's exercise uses.
    • A type overrides a trait's default method. A trait can supply a method body, and an impl block may either keep it or write its own. This is the closest match to classic overriding.

    The lesson's exercise

    The starter code has a Dog whose bark prints Woof! I am and a name. A Puppy holds a Dog in a field called dog, and a new function builds one. Calling buddy.bark() does not compile yet, because Puppy has no bark of its own and nothing is inherited. The goal is a Puppy that prints Yip! I am Buddy.

    1. Declare the method inside impl Puppy: fn bark(&self) { }. The &self parameter borrows the puppy for the duration of the call.
    2. Fill the body. The name is stored in the inner dog, so it is reached with self.dog.name. The body prints Yip! I am followed by that name using a {} placeholder in println!.

    The solution in Rust

    struct Dog {
        name: String,
    }
    
    impl Dog {
        fn bark(&self) {
            println!("Woof! I am {}", self.name);
        }
    }
    
    struct Puppy {
        dog: Dog,
    }
    
    impl Puppy {
        fn new(name: &str) -> Self {
            Self {
                dog: Dog {
                    name: name.to_string(),
                },
            }
        }
    
        fn bark(&self) {
            println!("Yip! I am {}", self.dog.name);
        }
    }
    
    fn main_function() {
        let buddy = Puppy::new("Buddy");
        buddy.bark();
    }
    

    The output is:

    Yip! I am Buddy
    

    The Dog version never runs, because buddy is a Puppy and the compiler resolves buddy.bark() to Puppy::bark. To get the dog's behaviour as well, the new method could call self.dog.bark() before or after its own printing. That is the Rust counterpart of calling super.bark().

    Overriding with traits

    When several types should share a method name and a default behaviour, put the method in a trait. An impl block gets the default for free and replaces it by defining the method again:

    trait Animal {
        fn name(&self) -> String;
        fn describe(&self) {
            println!("I am {}", self.name());
        }
    }
    
    struct Dog;
    struct Puppy;
    
    impl Animal for Dog {
        fn name(&self) -> String { "a dog".to_string() }
    }
    
    impl Animal for Puppy {
        fn name(&self) -> String { "a puppy".to_string() }
        fn describe(&self) {
            println!("Yip! I am {}", self.name());
        }
    }
    
    fn main() {
        Dog.describe();
        Puppy.describe();
    }
    

    This prints:

    I am a dog
    Yip! I am a puppy
    

    Here name is required: every implementor must provide it. describe has a default body that Dog keeps and Puppy overrides. The default body itself calls self.name(), so even an unmodified default produces output specific to each type. When code calls the method through a trait object, which is what the polymorphism lesson covers, the version that runs is chosen at run time from the real type.

    Common mistakes

    • Expecting the dog's method to be reachable as buddy.bark() without a definition. It is not. With no bark in impl Puppy, the compiler reports no method named `bark` found for struct `Puppy`.
    • Changing the signature. In a trait implementation, the method must match the trait's signature: the same parameters, the same &self or &mut self, and the same return type. A mismatch is a compile error, not a new method.
    • Forgetting the field path. Inside Puppy, self.name does not exist. The name belongs to the inner dog, so it is self.dog.name.
    • Dropping the space or the name. The lesson's tests expect Yip! I am Buddy exactly, so the text in front of the placeholder ends with a space.
    • Mixing up the two forms. Two inherent methods with the same name on different structs are unrelated. Only a shared trait lets one piece of code call either through the same name.

    Key takeaways

    • Rust has no inheritance, so "overriding" means defining the behaviour a type should have.
    • A struct's own impl block can define any method name without clashing with another type's.
    • A trait default method can be kept or replaced by each implementor, which is the closest match to overriding in class-based languages.
    • To reuse the wrapped value's behaviour, call it explicitly through the field.
    Keep going with RustAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free