Debug School

rakesh kumar
rakesh kumar

Posted on

Traits in Rust: what, why, when (simple)

Trait = a contract (behavior).
It says: “Any type that implements this trait must provide these methods.”

Why we use traits

Write reusable code: one function works for many types.

Polymorphism (like interfaces): treat different types the same way if they share behavior.

Operator overloading: +, ==, println! etc. work via traits (Add, PartialEq, Display).

Extending types: add methods to existing types using an “extension trait”.

Cleaner architecture: separate behavior from data types.

When we use traits

When you want a function to accept multiple different types that behave similarly.

When you want compile-time safety + speed (static dispatch via generics).

When you want *runtime polymorphism *(dynamic dispatch via dyn Trait) for plugin-like systems.

When you need standard behavior (printing, cloning, comparing, iterating).

Basic trait + implementation

trait Speak {
    fn speak(&self) -> &'static str;
}

struct Dog;
struct Cat;

impl Speak for Dog {
    fn speak(&self) -> &'static str { "Woof" }
}
impl Speak for Cat {
    fn speak(&self) -> &'static str { "Meow" }
}

fn main() {
    let d = Dog;
    let c = Cat;
    println!("{}", d.speak());
    println!("{}", c.speak());
}
Enter fullscreen mode Exit fullscreen mode

Output

Woof
Meow
Enter fullscreen mode Exit fullscreen mode

2) Trait as function parameter (generic bound)

trait Area {
    fn area(&self) -> i32;
}

struct Square(i32);
struct Rect(i32, i32);

impl Area for Square {
    fn area(&self) -> i32 { self.0 * self.0 }
}
impl Area for Rect {
    fn area(&self) -> i32 { self.0 * self.1 }
}

fn print_area<T: Area>(shape: &T) {
    println!("Area = {}", shape.area());
}

fn main() {
    print_area(&Square(5));
    print_area(&Rect(4, 6));
}
Enter fullscreen mode Exit fullscreen mode

Output

Area = 25
Area = 24
Enter fullscreen mode Exit fullscreen mode

3) Default method in a trait

trait Greet {
    fn name(&self) -> &str;

    fn greet(&self) {
        println!("Hello, {}", self.name());
    }
}

struct User { name: String }

impl Greet for User {
    fn name(&self) -> &str { &self.name }
}

fn main() {
    let u = User { name: "Ashwani".to_string() };
    u.greet();
}
Enter fullscreen mode Exit fullscreen mode

Output

Hello, Ashwani
Enter fullscreen mode Exit fullscreen mode

4) Trait + impl Trait return type

trait Label {
    fn label(&self) -> String;
}

struct Product { id: i32 }

impl Label for Product {
    fn label(&self) -> String { format!("Product#{}", self.id) }
}

fn make_labeler(id: i32) -> impl Label {
    Product { id }
}

fn main() {
    let x = make_labeler(101);
    println!("{}", x.label());
}
Enter fullscreen mode Exit fullscreen mode

Output

Product#101
Enter fullscreen mode Exit fullscreen mode

5) Trait objects (dyn Trait) for runtime polymorphism

trait Pay {
    fn pay(&self) -> i32;
}

struct Cash(i32);
struct Card(i32);

impl Pay for Cash { fn pay(&self) -> i32 { self.0 } }
impl Pay for Card { fn pay(&self) -> i32 { self.0 } }

fn total_payments(items: Vec<Box<dyn Pay>>) -> i32 {
    items.into_iter().map(|x| x.pay()).sum()
}

fn main() {
    let items: Vec<Box<dyn Pay>> = vec![Box::new(Cash(200)), Box::new(Card(500))];
    println!("Total = {}", total_payments(items));
}
Enter fullscreen mode Exit fullscreen mode

Output

Total = 700
Enter fullscreen mode Exit fullscreen mode

6) Associated types in traits

trait Convert {
    type Output;
    fn convert(&self) -> Self::Output;
}

struct Meter(i32);

impl Convert for Meter {
    type Output = i32; // centimeters
    fn convert(&self) -> i32 { self.0 * 100 }
}

fn main() {
    let m = Meter(3);
    println!("{}", m.convert());
}
Enter fullscreen mode Exit fullscreen mode

Output

300

Enter fullscreen mode Exit fullscreen mode

7) Trait bounds with multiple traits (Display + Clone)

use std::fmt::Display;

fn print_twice<T: Display + Clone>(x: T) {
    println!("{}", x.clone());
    println!("{}", x);
}

fn main() {
    print_twice(String::from("Rust"));
}
Enter fullscreen mode Exit fullscreen mode

Output

Rust
Rust
Enter fullscreen mode Exit fullscreen mode

8) Implementing standard library trait Display (custom printing)
use std::fmt;

struct Point { x: i32, y: i32 }

impl fmt::Display for Point {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "({}, {})", self.x, self.y)
    }
}

fn main() {
    let p = Point { x: 5, y: 7 };
    println!("Point = {}", p);
}
Enter fullscreen mode Exit fullscreen mode

Output

Point = (5, 7)
Enter fullscreen mode Exit fullscreen mode

9) Extension trait (add new method to existing type)

trait MyStrExt {
    fn shout(&self) -> String;
}

impl MyStrExt for str {
    fn shout(&self) -> String {
        format!("{}!!!", self.to_uppercase())
    }
}

fn main() {
    let s = "hello";
    println!("{}", s.shout());
}
Enter fullscreen mode Exit fullscreen mode

Output

HELLO!!!
Enter fullscreen mode Exit fullscreen mode

10) Generic algorithm using trait PartialOrd (works for many types)

fn max_of<T: PartialOrd>(a: T, b: T) -> T {
    if a > b { a } else { b }
}

fn main() {
    println!("{}", max_of(10, 20));
    println!("{}", max_of(5.5, 3.2));
}
Enter fullscreen mode Exit fullscreen mode

Output

20
5.5
Enter fullscreen mode Exit fullscreen mode

Top comments (0)