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::barkandDog::barknever 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
implblock 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.
- Declare the method inside
impl Puppy:fn bark(&self) { }. The&selfparameter borrows the puppy for the duration of the call. - Fill the body. The name is stored in the inner dog, so it is reached with
self.dog.name. The body printsYip! I amfollowed by that name using a{}placeholder inprintln!.
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 nobarkinimpl Puppy, the compiler reportsno 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
&selfor&mut self, and the same return type. A mismatch is a compile error, not a new method. - Forgetting the field path. Inside
Puppy,self.namedoes not exist. The name belongs to the inner dog, so it isself.dog.name. - Dropping the space or the name. The lesson's tests expect
Yip! I am Buddyexactly, 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
implblock 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.
