Trait/Interface in Rust

Interface/class in Rust(declared with keyword trait) having Virtual Functions(not pure Virtual) declared or defined inside it. These functions are implemented(overridden) on type.
A trait is a way to define shared behavior across different data types

// 1. Define the trait (the shared behavior)
trait Speak {
    fn make_sound(&self);             // Method signature with no body yet
}

struct Dog;
struct Cat;

// 2. Implement the trait for the Dog
impl Speak for Dog {
    fn make_sound(&self) {
        println!("Woof!");
    }
}

// 3. Implement the same trait for the Cat
impl Speak for Cat {
    fn make_sound(&self) {
        println!("Meow!");
    }
}

fn main() {
    let dog = Dog;
    let cat = Cat;

    // Both types can use the exact same method name
    dog.make_sound(); // Prints: Woof!
    cat.make_sound(); // Prints: Meow!
}

Usage of Traits
Once a trait is defined, you can write generic functions that accept any type, as long as it implements that trait:

// This function doesn't care if you pass a Dog or a Cat, 
// as long as it is an animal that can Speak!
fn animal_talk(animal: &impl Speak) {
    animal.make_sound();
}

Rust Lib provided Traits

Using Traits

Passing trait as argument to function

// trait T defined above
pub fn fun(t: &impl T) {
    t.test();
}
fn main() {
    let a = S {
        x: 1,
        y: 2,
    };
    fun(&a);
}
$ cargo run
test()

Returning trait from function


// trait T defined above
// This function can return struct of type S also
pub fn ret_trait() -> impl T {
    S {
        x: 1,
        y: 2,
    }
}
            

Trait Bound (Same as C++20 Concepts)

Rule we put on a function or structure that tells the Rust compiler: "This function should accept a data type, and the data type should implement this specific trait."

// 1. Define the trait
trait Area {
    fn get_area(&self) -> f64;
}

struct Square { side: f64, }
impl Area for Square {          // Implement the trait for square
    fn get_area(&self) -> f64 {
        self.side * self.side
    }
}

struct Circle { radius: f64, }
impl Area for Circle {
    fn get_area(&self) -> f64 {
        3.14159 * self.radius * self.radius
    }
}

// Trait bound (Syntax-1)
// variable (a) should be of type T=area
// The `T: Area` restriction forces T to be of type that implements the Area trait.
fn print_area <T: Area> (a: T) {
	println!("{}", a.get_area())
}

fn main() {
    let my_square = Square { side: 4.0 };
    let my_circle = Circle { radius: 2.0 };

    print_area(my_square);
    print_area(my_circle);

    // If you try to pass a string or number, COMPILE TIME Error
    // Rust will block it because a String doesn't implement Area.
    // print_area("hello");
}
Alternative Syntax: The where Clause
When you start adding multiple trait bounds, the <T: Area> syntax can get messy.
Rust provides a cleaner way to write trait bounds using a where clause at the end of the function signature.
These two lines mean the exact same thing to the compiler, but the second one is often easier to read:
// Trait bound
// Syntax-2
// variable (a) should be of type T=area
fn print_area1 <T> (a: T)
where
	T: Area 
{
	println!("{}", a.get_area())
}

Taking 2 or more Traits (using +)

use std::fmt::Display;
trait Area {
	fn get_area(&self) -> f64;
}
struct Square { side:f64 }
impl Area for Square {
	fn get_area(&self) -> f64 {
		self.side * self.side
	}
}
impl Display for Square {
	fn fmt (&self, f: &mut std::fmt::Formatter <'_>) -> std::fmt::Result {
		write!(f, "Square with side length {}", self.side)
	}
}

// This function takes 2 traits
fn print_area <T> (a:T) 
where
	T: Area + Display
{
	println!("{}", a);
	println!("area: {}", a.get_area());
}
fn main() {
	let sq = Square {side: 4.0};
	print_area(sq);
}