Skip to content

Latest commit

 

History

History
150 lines (112 loc) · 3.45 KB

File metadata and controls

150 lines (112 loc) · 3.45 KB

Traits

A trait defines the functionality a particular type has and can share with other types. We can use traits to define shared behaviour in an abstract way (just like abstract class in java). We can use trait bounds to specify that a generic type can be any type that has certain behaviou.

Note: Traits are similar to a feature often called interfaces in other languages (eg, typescript), although with some differences.


1. Defining a Trait & Implementing It

pub trait Summary {
  fn summarize(&self) -> String;
}

struct User {
  name: String,
  age: u32,
}

impl Summary for User { // for the user struct we have implemented the summary trait
  fn summarize(&self) -> String { 
    return format!("User {} is {} years old" , self.name, self.age);
  }
}

fn main () {
  let user = User { 
    name: String::from("Tushar"),
    age: 21,
  };
  println!("{}", user.summarize()); // whenever we define user, we can call summarize function
}

Once a type implements a trait, you can call the trait method on instances of that type.


2. Traits with Default Implementations

pub trait Summary {
  fn summarize(&self) -> String { // default implementation
     return String::from("Summarize");
  }
}

struct User {
  name: String,
  age: u32,
}

impl Summary for User{}

fn main () {
  let user = User { 
    name: String::from("Tushar"),
    age: 21,
  };
  println!("{}", user.summarize()); // whenever we define user, we can call summarize function
}

what does it do?

  • whoever is implementing Summary, in our case, its User, have their own implementation then that will be used
  • if they dont have their own implen=mentation then the default implementation will be used

3. Traits as Parameters

We can use traits as function parameters to accept any type that implements that trait.

pub trait Summary {
  fn summarize(&self) -> String;
}

struct User {
  name: String,
  age: u32,
}
  
impl Summary for User {
  fn summarize(&self) -> String {
    return format!("User {} is {} years old" , self.name, self.age);
  }
}

pub fn notify(item: &impl Summary) { // this function takes sometjing as an input which implements "Summary" 
  println!("Breaking News! {}", item.summarize());
}

fn main() {
  let user = User {
    name: String::from("Tushar"),
    age: 21,
  };
  notify(&user);
}

&impl TraitName in parameters is syntactic sugar for trait bounds. This is helpful when we don't need to specify the exact type, just the behavior it supports.


4. Trait Bound Syntax

We can bind generic types to traits using trait bounds. This means the generic type must implement a specific trait.


pub fn notify<T: Summary>(item: T) {
    // Takes a generic T which must implement the Summary trait
    println!("Breaking News! {}", item.summarize());
}

In the above code:

  • T is a generic type parameter
  • T: Summary is the trait bound, ensuring T implements the Summary trait

Multiple Trait Bounds

We can bind multiple traits using the + syntax:

pub fn notify<T: Summary + Fix>(item: &T) {
    println!("Breaking News! {}", item.summarize());
}

This means T must implement both Summary and Fix traits.


We can also use where clauses for cleaner syntax if the trait bounds get too long.

pub fn notify<T>(item: &T)
where
    T: Summary + Fix,
{
    println!("Breaking News! {}", item.summarize());
}