Confusion : Struct, impl, self, trait

can you please explain struct , impl , self,trait. self confuse me in structure in rust when we impl any struct and create any function what that method exaclly do when we supplies self in that method .please explain me. thank you.

3 Likes

The book has a chapter on method syntax, as well as on structs and traits.

In general, the book is a useful resource and you should read it or at least use it as a reference from time to time.

1 Like

Here is a very crude explanation:

Structs: Data structures, a bit like classes in other languages. Structs are essentially, the basic structures for Object Orient Programming in Rust.
Trait: Obvious enough, is a trait. For example, it might be can roll tongue or maybe blue eyes.
Impl: This can let you implement a trait for a struct. So, you can implement the trait have tails for the struct dogs.
Self: Self is usually the object instance it self. When you create a function/procedure for a dog, you should add self to the list of arguments. For example: fn wag_tail(&self).

Hopefully that solves your confusion.

5 Likes

Structs are essentially struct from C/C++. They just say this type has certain fields. Lets start by defining a character in a sort of a dungeon like single player console game.

 struct Player { 
     name: String,
     health: u32,
     damage: u32,
 }

Impl are the way you attach methods to a struct or a trait. Impls can get complex, because you can add implementation to generic stuff, which is super useful, but really mind boggling as well so I won't get into it. For example I'll add a 'constructor' for my player so I can create new players.

  impl Player {
     pub fn new(name: String) -> Player {
           Player {
                  name: name,
                  health: 1,
                  damage: 1,
           }
     }
 }

We can now create let player = Player::new("Sam"); However you'll note that new is called like Player::new. That's because methods without self argument are called using convention <STRUCT_NAME>::<METHOD_NAME> (replace <STRUCT_NAME>/<METHOD_NAME> are placeholders).

If we had something like this:

 impl Player {
       pub fn incr_health(&mut self) {
           self.health += 1;
       }
 }

you could call let mut player = Player::new("Sam"); player.incr_health();. That's because method that have self, &self or &mut self can be called using conventions <STRUCT_NAME>.<SELF_METHOD_NAME> (e.g. player.incr_health()).

But you might wonder, where did our &mut self parameter go. It's still there if you squint really hard.

     +-----------------------+
     |                       |
   player.incr_health();     |
              |              |
              V              V
  Player::incr_health(&mut player);

Dot syntax is just a shortcut to avoid writing the full method name every time!

Trait are a way to give different stuff similar interface. Let's say we complicated matters and add a special struct called Crate

 struct Crate {
      health: u32,
      loot: String,
 }

Crates are stuff our player can attack and destroy to get sweet, sweet loot stored inside them. But wait, couldn't we also make Player be attackable? So if for example a fire breathing Gargoyle appears, it would make sense that his area attack breaks the doodads for us, but also damages player. Also we could write a nice message when something Damagable dies (takes more damage than health). Let's do that:

trait Damagable {
   fn get_health(&self) -> u32;       
   fn set_health(&mut self, hp: u32);
   fn on_death(&self);

   fn damage(&mut self, damage: u32) {
      let hp = self.get_health();

      if hp <= damage {
          self.set_health(0);
          self.on_death();
      } else {
          self.set_health(hp-damage);
      }
   }
}

Without going into too much detail, what Damagable trait does is asks for us to have methods like get_health, set_health and on_death, while providing a default way to deal with damage. So lets implement it for Crate.

impl Damagable for Crate {
    fn get_health(&self) -> u32 {
       return self.health;
    }
    
    fn set_health(&mut self, hp: u32) {
       self.health = hp;
    }
    
    fn on_death(&self) {
       println!("You got {}", self.loot);
    }
}

And that's how you implement a Trait. It's a bit different from impl Struct, but works similar. One thing to remember is that all methods on Traits are public by default. Methods implemented structs (using impl Struct) are private by default.

Implementing it for Player is exercise for the reader, but the solution is here.

Once those things are implemented we create a crate with loot:

let mut crate_of_swords = Crate {
    health: 1,
    loot: "Sword of Lesser Mediocrity".to_owned(),
};

And have our previously defined player attack it crate_of_swords.damage(player.damage). This will write on_death text, which in solution above will write: You got Sword of Lesser Mediocrity.

NOTE: This isn't a realistic depiction of how Game Engines work, this is a 20min thing to explain how you use traits/structs.

25 Likes

Thank you.