Rust - Impl

eslerkang·2022년 3월 10일
0

Rust

목록 보기
8/10
post-thumbnail

Impl

Impl means implement and it is used to define implementation on types.
We need to use impl to define methods into struct/enum.

#[derive(Debug)]
struct Animal {
	age: u8,
    animal_type: AnimalType
}

#[derive(Debug)]
enum AnimalType {
	Cat,
    Dog
}

fn main() {}

This is simple example of struct and enum.
As we want to print struct with println!("{:?}", animal) debug print, we have to write derive(Debug) above. Also, as Animal is including AnimalType enum, we have to write derive(Debug) above the enum too. If you didn't, you'll see this error message 'AnimalType doesn't implement Debug'.

At this situation, we can make Animal instance like this

#[derive(Debug)]
struct Animal {
	age: u8,
    animal_type: AnimalType
}

#[derive(Debug)]
enum AnimalType {
	Cat,
    Dog
}

fn main() {
	let dog = Animal {
    	age: 1,
        animal_type: AnimalType::Dog
    };
    println!("My pet is {:#?}", dog);
}

If you want to create an instance like python or js, you can create a function with impl.

#[derive(Debug)]
struct Animal {
	age: u8,
    animal_type: AnimalType
}

#[derive(Debug)]
enum AnimalType {
	Cat,
    Dog
}

impl Animal {
	fn new(age: u8, animal_type: AnimalType) -> Self {
    	Self {
        	age,
            animal_type
        }
    }
}

fn main() {}

By using impl Animal, we can make methods into Animal struct.
Self means Animal, so we can use Self{age, animal_type} to return an Animal type value(we just wrote age, animal_type cause the property names are same as variables).
But at this point, we don't have Animal instance to make it self, we cannot use dot operator.
This is how we can use that function.

#[derive(Debug)]
struct Animal {
	age: u8,
    animal_type: AnimalType
}

#[derive(Debug)]
enum AnimalType {
	Cat,
    Dog
}

impl Animal {
	fn new(age: u8, animal_type: AnimalType) -> Self {
    	Self {
        	age,
            animal_type
        }
    }
}

fn main() {
	let dog = Animal::new(1, AnimalType::Dog);
    println!("My pet is {:#?}", dog);
}

Also, we can make the new function more specific.

#[derive(Debug)]
struct Animal {
	age: u8,
    animal_type: AnimalType
}

#[derive(Debug)]
enum AnimalType {
	Cat,
    Dog
}

impl Animal {
	fn new_dog(age: u8) -> Self {
    	Self {
        	age,
            animal_type: AnimalType::Dog
        }
    }
    
	fn new_cat(age: u8) -> Self {
    	Self {
        	age,
            animal_type: AnimalType::Cat
        }
    }
}

fn main() {
	let dog = Animal::new_dog(1);
    println!("My pet is {:#?}", dog);
}

Now, we can make instances so we'll try to make method.

#[derive(Debug)]
struct Animal {
	age: u8,
    animal_type: AnimalType
}

#[derive(Debug)]
enum AnimalType {
	Cat,
    Dog
}

impl Animal {
	fn new_dog(age: u8) -> Self {
    	Self {
        	age,
            animal_type: AnimalType::Dog
        }
    }
    
	fn new_cat(age: u8) -> Self {
    	Self {
        	age,
            animal_type: AnimalType::Cat
        }
    }
    
    fn print(&self) {
    	println!("My pet is {:#?}", self);
    }
}

fn main() {
	let dog = Animal::new_dog(1);
    println!("My pet is {:#?}", dog);
}

Now we made our first method in Animal(we can use dot operator). Method print is receiving &self. And it means reference of dog. So just writing dog.print();, we can print our dog. Also it's syntactic sugar. The original shape of this syntax is Animal::print(&dog);.

And when you need to change the original variable in the method, you have to change our code like this.

#[derive(Debug)]
struct Animal {
	age: u8,
    animal_type: AnimalType
}

#[derive(Debug)]
enum AnimalType {
	Cat,
    Dog
}

impl Animal {
	fn new_dog(age: u8) -> Self {
    	Self {
        	age,
            animal_type: AnimalType::Dog
        }
    }
    
	fn new_cat(age: u8) -> Self {
    	Self {
        	age,
            animal_type: AnimalType::Cat
        }
    }
    
    fn print(&self) {
    	println!("My pet is {:#?}", self);
    }
    
    fn change_to_cat(&mut self) {
    	self.animal_type = AnimalType::Cat;
	}
}

fn main() {
	let mut dog = Animal::new_dog(1);
    println!("My pet is {:#?}", dog);
    dog.change_to_cat();
    println!("My pet is {:#?}", dog);
}

At this example, our method change_to_cat want to modify our variable dog. So we have to make the variable mutable, and method should receive &mut self to mutate self.

Like impl at struct, we can make impl at enum.

#[derive(Debug)]
struct Animal {
  age: u8,
  animal_type: AnimalType
}

#[derive(Debug)]
enum AnimalType {
  Cat,
  Dog
}

impl AnimalType {
  fn check_type(&self) {
    use AnimalType::*;

    match self {
      Cat => println!("a cat..?!"),
      Dog => println!("a dog..?!")
    }
  }
}

impl Animal {
  fn new(age: u8, animal_type: AnimalType) -> Self {
    Self {
      age,
      animal_type
    }
  }
}

fn main() {
  use AnimalType::*;
  let cat = Animal::new(1, Cat);
  cat.animal_type.check_type();
}

At this example, we made check_type method in the impl of AnimalType. And as it receive &self, we have to use it at animal_type property of our Animal variable.
So we created cat variable and cat.animal_type.check_type(); to use our method.
The self in this method means AnimalType instance that is calling this method. At this moment, it means animal_type of cat.

Plus

One feature of impl is that we can make multiple impls in one struct or enum.

#[derive(Debug)]
struct Animal {
  age: u8,
  animal_type: AnimalType
}

#[derive(Debug)]
enum AnimalType {
  Cat,
  Dog
}

impl Animal {
  fn new_old_cat() -> Self {
    Self {
      age: 16,
      animal_type: AnimalType::Cat
    }
  }
}

impl Animal {
  fn new_cat(age: u8) -> Self {
    Self {
      age,
      animal_type: AnimalType::Cat
    }
  }

  fn new_dog(age: u8) -> Self {
    Self {
      age,
      animal_type: AnimalType::Dog
    }
  }

  fn print(&self) {
    println!("I'm an {:#?}", self);
  }
}

In this example, we can see two impl of Animal. It works very well and by some reason if you want to sepereate the impl, you can write like this.

profile
Surfer surfing on the dynamic flow of the world

0개의 댓글