Default Implementations
Part of the Object Oriented Programming section of Coddy's Rust journey — lesson 31 of 61.
So far, every trait method we've defined has been just a signature—a requirement with no body. But what if most types implementing a trait would use the same logic? Rust lets you provide a default implementation directly in the trait definition.
Instead of ending the method signature with a semicolon, you add a body:
trait Greet {
fn greet(&self) -> String {
String::from("Hello there!")
}
}
Now any type that implements Greet automatically gets this method—without writing any code in the impl block:
struct Guest;
impl Greet for Guest {}
let visitor = Guest;
println!("{}", visitor.greet()); // Hello there!
The empty impl Greet for Guest {} is valid because the trait already provides the behavior. The struct simply "opts in" to the trait and receives the default method for free.
This pattern is useful when you want to define shared behavior that works for most cases. Types that need something different can override the default (which you'll learn next), while types that are happy with the standard behavior don't need to repeat themselves.
Challenge
EasyLet's explore the power of default implementations by building a notification system! You'll create a trait that provides standard behavior out of the box, so types can opt in without writing repetitive code.
You'll organize your code across three files:
notifiable.rs: Define a publicNotifiabletrait with anotify(&self) -> Stringmethod that has a default implementation. The default should return"You have a new notification!". Any type implementing this trait will automatically receive this behavior without needing to write anything in their impl block.alerts.rs: Create two simple public unit structs that implement your trait:EmailAlert— implementsNotifiablewith an empty impl block (uses the default)SystemAlert— also implementsNotifiablewith an empty impl block (uses the default)
main.rs: Bring your modules together and demonstrate how both alert types share the same default behavior. Create instances of both structs and call theirnotifymethods.
In your main file, create an EmailAlert and a SystemAlert, then print their notifications on separate lines.
Your output should be:
You have a new notification!
You have a new notification!Notice how both types produce identical output—that's the beauty of default implementations! Neither struct needed to write any code for the notify method, yet both have fully functional notification behavior. In the next lesson, you'll learn how to override these defaults when a type needs custom behavior.
Cheat sheet
Trait methods can have default implementations by providing a method body instead of just a signature:
trait Greet {
fn greet(&self) -> String {
String::from("Hello there!")
}
}
Types implementing the trait automatically receive the default behavior without writing any code:
struct Guest;
impl Greet for Guest {}
let visitor = Guest;
println!("{}", visitor.greet()); // Hello there!
An empty impl block is valid when the trait provides default implementations. This allows types to opt in to shared behavior without repetition.
Try it yourself
mod notifiable;
mod alerts;
use notifiable::Notifiable;
use alerts::{EmailAlert, SystemAlert};
fn main() {
// TODO: Create an instance of EmailAlert
// TODO: Create an instance of SystemAlert
// TODO: Print the notification from EmailAlert
// TODO: Print the notification from SystemAlert
}
This lesson includes a short quiz. Start the lesson to answer it and track your progress.
All lessons in Object Oriented Programming
1Methods and Behavior
Intro Implementation BlocksThe Self ParameterMutable MethodsAssociated FunctionsMultiple Implementation BlocksMethod ChainingRecap - Rectangle Actions4Project: Virtual Pet
Defining the PetFeeding the Pet7Standard Traits
The Debug TraitThe Display TraitClone and CopyEquality TraitsRecap - Printable Point10Project: Document System
The Draw TraitText Component2Encapsulation and Modules
Modules BasicsThe Public KeywordPrivate FieldsGettersSettersRecap - Secure Locker5Generics
Generic StructsGeneric MethodsMultiple Generic TypesGeneric FunctionsRecap - Coordinate Point8Traits as Bounds
Trait Bounds SyntaxMultiple BoundsThe Where ClauseReturning Types with TraitsRecap - Generic Printer11Design Patterns in Rust
Newtype PatternCompositionThe Drop TraitFrom and IntoRecap - Smart Pointer Mock3Advanced Enums
Enums with DataMethods on EnumsMatching Data VariantsThe Option Enum RevisitedRecap - Shape Enum6Traits Definition
What is a Trait?Implementing TraitsDefault ImplementationsOverriding DefaultsTraits with ParametersRecap - Media Player