Rust Traits

What are Traits in Rust?

A trait in Rust is a collection of methods that define shared functionality. Any type that implements a trait must provide the functionality defined by the trait. Traits are useful for enabling polymorphism, making your code more modular and reusable.

Key Features of Traits

  1. Abstraction: Traits define behavior without specifying implementation.
  2. Polymorphism: Traits allow working with multiple types that share common behavior.
  3. Static Dispatch: Rust uses traits with zero runtime cost by resolving implementations at compile time.
  4. Code Reuse: Traits enable shared functionality across multiple types.

Defining a Trait

A trait is defined using the trait keyword.

Example:

trait Greet {
fn greet(&self);
}

Implementing a Trait

To implement a trait for a struct, use the impl keyword.

Example:

trait Greet {
fn greet(&self);
}

struct Person {
name: String,
}

impl Greet for Person {
fn greet(&self) {
println!("Hello, my name is {}.", self.name);
}
}

fn main() {
let person = Person {
name: String::from("Alice"),
};

person.greet();
}

Output:

Hello, my name is Alice.

Traits with Default Method Implementations

Traits can include default implementations for methods, which allows types to use the default behavior or override it.

Example:

trait Greet {
fn greet(&self) {
println!("Hello!");
}
}

struct Dog;

impl Greet for Dog {}

fn main() {
let dog = Dog;
dog.greet(); // Uses the default implementation
}

Output:

Hello!

Traits with Multiple Methods

A trait can define multiple methods.

Example:

trait Calculator {
fn add(&self, a: i32, b: i32) -> i32;
fn multiply(&self, a: i32, b: i32) -> i32;
}

struct Math;

impl Calculator for Math {
fn add(&self, a: i32, b: i32) -> i32 {
a + b
}

fn multiply(&self, a: i32, b: i32) -> i32 {
a * b
}
}

fn main() {
let math = Math;
println!("Addition: {}", math.add(5, 10));
println!("Multiplication: {}", math.multiply(5, 10));
}

Output:

Addition: 15
Multiplication: 50

Trait Bounds

Trait bounds allow you to specify that a generic type must implement a specific trait. This ensures that only types with the required behavior can be used.

Example:

trait Printable {
fn print(&self);
}

struct Book {
title: String,
}

impl Printable for Book {
fn print(&self) {
println!("Book: {}", self.title);
}
}

fn print_item<T: Printable>(item: T) {
item.print();
}

fn main() {
let book = Book {
title: String::from("Rust Programming"),
};

print_item(book);
}

Output:

Book: Rust Programming

Trait Objects

Trait objects allow you to use dynamic dispatch to work with multiple types that implement the same trait. They are defined using Box<dyn Trait>.

Example:

trait Sound {
fn make_sound(&self);
}

struct Cat;
struct Dog;

impl Sound for Cat {
fn make_sound(&self) {
println!("Meow!");
}
}

impl Sound for Dog {
fn make_sound(&self) {
println!("Woof!");
}
}

fn main() {
let animals: Vec<Box<dyn Sound>> = vec![
Box::new(Cat),
Box::new(Dog),
];

for animal in animals {
animal.make_sound();
}
}

Output:

Meow!
Woof!

Deriving Traits

Rust provides built-in traits like Debug, Clone and PartialEq, which can be automatically implemented for your types using the #[derive] attribute.

Example:

#[derive(Debug)]
struct Point {
x: i32,
y: i32,
}

fn main() {
let point = Point { x: 5, y: 10 };
println!("{:?}", point);
}

Output:

Point { x: 5, y: 10 }

Leave a Comment

BoxofLearn