Menu
Coddy logo textTech

Overriding Defaults

Part of the Object Oriented Programming section of Coddy's Rust journey — lesson 32 of 61.

Default implementations are convenient, but sometimes a type needs behavior that differs from the standard. When this happens, you can override the default by providing your own implementation in the impl block.

Simply define the method with the same signature, but include your custom logic:

trait Greet {
    fn greet(&self) -> String {
        String::from("Hello there!")
    }
}

struct Robot {
    id: u32,
}

impl Greet for Robot {
    fn greet(&self) -> String {
        format!("BEEP BOOP. Unit {} online.", self.id)
    }
}

Even though Greet provides a default greet method, Robot replaces it entirely with its own version. When you call greet() on a Robot, Rust uses the custom implementation:

let bot = Robot { id: 42 };
println!("{}", bot.greet());  // BEEP BOOP. Unit 42 online.

This gives you flexibility—types that are happy with the default don't need to write anything, while types that need specialized behavior can override just the methods they care about. The trait still guarantees that every implementing type has the method available.

challenge icon

Challenge

Easy

Let's build a notification system where different alert types can customize their messages! You'll create a trait with a default implementation, then have one type use the default while another overrides it with custom behavior.

You'll organize your code across three files:

  • notifiable.rs: Define a public Notifiable trait with a notify(&self) -> String method that has a default implementation returning "Alert: Something happened!".
  • alerts.rs: Create two public structs that implement your trait differently:
    • GenericAlert — a unit struct that uses the default notification (empty impl block)
    • UrgentAlert — a struct with a public message field (String) that overrides the default to return "URGENT: {message}" where {message} is its stored message
  • main.rs: Bring your modules together and demonstrate both behaviors. Create a GenericAlert and an UrgentAlert using the input provided, then print their notifications.

The key insight here is that GenericAlert gets free behavior from the default, while UrgentAlert provides its own specialized version by defining the method in its impl block.

Your output should follow this format:

Alert: Something happened!
URGENT: {message}

For example, with input Server is down!:

Alert: Something happened!
URGENT: Server is down!

You will receive one input: the message for the urgent alert.

Cheat sheet

You can override default trait implementations by providing your own implementation in the impl block:

trait Greet {
    fn greet(&self) -> String {
        String::from("Hello there!")
    }
}

struct Robot {
    id: u32,
}

impl Greet for Robot {
    fn greet(&self) -> String {
        format!("BEEP BOOP. Unit {} online.", self.id)
    }
}

let bot = Robot { id: 42 };
println!("{}", bot.greet());  // BEEP BOOP. Unit 42 online.

Types that don't need custom behavior can use an empty impl block to accept the default implementation, while types requiring specialized behavior can override specific methods by defining them with the same signature.

Try it yourself

mod notifiable;
mod alerts;

use alerts::{GenericAlert, UrgentAlert};
use notifiable::Notifiable;

fn main() {
    // Read input for the urgent alert message
    let mut input = String::new();
    std::io::stdin().read_line(&mut input).expect("Failed to read line");
    let message = input.trim().to_string();

    // TODO: Create a GenericAlert instance

    // TODO: Create an UrgentAlert instance with the input message

    // TODO: Print the notification from GenericAlert

    // TODO: Print the notification from UrgentAlert
}
quiz iconTest yourself

This lesson includes a short quiz. Start the lesson to answer it and track your progress.

All lessons in Object Oriented Programming