TRAITS

 TRAITS

  • A trait defines funtionality a particular type can share with other types.
  • Used to define shared behaviour in an abstract way.
  • Also called interface in other languages.
  • struct Person{
      first_name: String,
      last_name: String,
      age: i32
    }
    
    trait FullFunction {
      fn full_name(&self) -> String;
    }
    
    impl FullFunction for Person{
      fn full_name(&self) -> String{
        format!("{} {}"), self.first_name, self.last_name)
      }
    }
  • The above code block shows a basic example of trait implementation and its usage.
  • struct Person{
      first_name: String,
      last_name: String,
      age: i32
    }
    
    trait CanInitializeWithFullName{
      fn new(fullname: &str) -> Self;
    }
    
    impl CanInitializeWithFullName for Person{
      fn new(fullname: &str) -> Self{
        let parts: Vec<&str> = full_name.split(' ').collect();
        Person {
           first_name: parts.0,
           last_name: parts.1,
           age: 30
        }
      }
    }
    
    fn main(){
      let person: Person = Person::new("first_name: "Sanu Shilshad");
      println!("First Name: {} Last Name: {}, Age: {}", person.first_name, person.last_name, person.age);
    
    }
  • The above example shows how to initilize a Person struct using CanIntitalizeFullName trait function.
  • use std::fmt;
    
    struct Person{
      first_name: String,
      last_name: String,
      age: i32
    }
    
    trait CanInitializeWithFullName{
      fn new(fullname: &str) -> Self;
    }
    
    impl CanInitializeWithFullName for Person{
      fn new(fullname: &str) -> Self{
        let parts: Vec<&str> = full_name.split(' ').collect();
        Person {
           first_name: parts.0,
           last_name: parts.1,
           age: 30
        }
      }
    }
    
    impl fmt::Display for Person{
      fn fmt(&self, fmt:Formatter) -> fmt::Result{
        write!(f, "{} {}, years old", self.first_name, self.last_name, self.age)
      }
    }
    
    fn main(){
      let person: Person = Person::new("first_name: "Sanu Shilshad");
      println!("{}", person);
    }
  • The above example shows that you can print your own display for a structure using Display trait from std::fmt.
  • You can take trait as a parameter like below:
  • struct Person{
      first_name: String,
      last_name: String,
      age: i32
    }
    
    trait FullFunction {
      fn full_name(&self) -> String;
    }
    
    fn print_full_name_and_age(value: &impl HasFunction){
      println!("{}", value.full_name())
    }
    
    fn main(){
     let person: Person = Person::new(full_name:"Sanu Shilshad");
     print_full_name_and_age(&person);
    }
  • The trait parameter says that it takes any struct that has HasFunction implementation.
  • You can write the above code much better like below:
  • struct Person{
      first_name: String,
      last_name: String,
      age: i32
    }
    
    trait FullFunction {
      fn full_name(&self) -> String;
    }
    
    fn print_details<T: HasFunction>(value: &T){
      println!("{}", value.full_name())
    }
    
    fn main(){
     let person: Person = Person::new(full_name:"Sanu Shilshad");
     print_full_name_and_age(&person);
    }
  • The above method is called Trait Bound Box.
  • You can add multiple traits like below
  • struct Person{
      first_name: String,
      last_name: String,
      age: i32
    }
    
    trait FullFunction {
      fn full_name(&self) -> String;
    }
    
    trait CanRun{
      fn run(&self);
    }
    
    impl CanRun for Person{
      fn run(&self){
        todo!()
      }
    }
    fn print_details<T: HasFunction + CanRun>(value: &T){
      println!("{}", value.full_name());
       value.run();
    }
    
    fn main(){
     let person: Person = Person::new(full_name:"Sanu Shilshad");
    }
  • You can also rewrite the above code block like below:

  • struct Person{
      first_name: String,
      last_name: String,
      age: i32
    }
    
    trait FullFunction {
      fn full_name(&self) -> String;
    }
    
    trait CanRun{
      fn run(&self);
    }
    
    impl CanRun for Person{
      fn run(&self){
        todo!()
      }
    }
    
    fn print_details<T>(value: &T) 
    where 
      T: HasFullName + CanRun,
    {
      println!("{}", value.full_name());
       value,run();
    }
    
    fn main(){
     let person: Person = Person::new(full_name:"Sanu Shilshad");
     print_full_name_and_age(&person);
    }
  • Traits can be implemented on other traits like below:
  • trait HasName {
     fn first_name(&self) -> &str;
     fn last_name(&self) -> &str;
    }
    trait HasFullName
    where
       Self: HasName,
    {
      fn full_name(&self) -> &str;
    }
    
    impl<T> HasFullName for T
    where 
        T:HasName,
    {
       fn full_name(&self) -> String {
         format!("{} {}", self.first_name(), self.last_name())
       }
    }
  • This is called a binding a trait to another.
  • In the above example HasFullName is created as long as object has a HasName trait.
  • And the implemention of full_name method in HasFullName can be shown as above.
  • trait HasName {
     fn first_name(&self) -> &str;
     fn last_name(&self) -> &str;
    }
    trait HasFullName
    where
       Self: HasName,
    {
      fn full_name(&self) -> &str;
    }
    
    impl<T> HasFullName for T
    where 
        T:HasName,
    {
       fn full_name(&self) -> String {
         format!("{} {}", self.first_name(), self.last_name())
       }
    }
    
    impl HasName for Person{
      fn first_name(&self) -> &str {
        &self.first_name
      }
    
      fn last_name(&self) -> &str {
        &self.last_name
      }
    }
    fn main(){
     let person: Person = Person::new(full_name:"Sanu Shilshad");
     let full_name: String =  person.full_name();
     println!("{}", full_name)l
    }
  • The above example shows how the method in the HasFullName is being used by struct Person.

Comments