트레이트(trait)
트레이트로 메소드 공유하기
파이썬은 클래스를 상속해 공통된 메소드를 사용할 수 있지만, 러스트는 구조체의 상속이 되지 않습니다.
먼저 파이썬에서 다음과 같이 Person 을 상속하는 새로운 클래스 Student 를 선언합니다.
class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age
        self.alive = True
    def say_hello(self):
        print("Hello, Rustacean!")
    def get_older(self, year):
        self.age += year
class Student(Person):
    def __init__(self, name, age, major):
        super().__init__(name, age)
        self.major = major
    def say_hello(self):
        print(f"Hello, I am {self.name} and I am studying {self.major}")
Student 클래스는 새로운 프로퍼티 major 를 가지고 있고, Person 의 say_hello 메소드를 오버라이드하고 있습니다.
Rust는 하나의 struct를 상속하는 방법이 존재하지 않습니다. 즉 필드와 메소드를 다른 struct에 전달할 수 없습니다. 하지만 서로 다른 struct 타입들이 메소드를 공유할 수 있는 하나의 속성을 정의할 수 있는데, 바로 trait입니다. 러스트에서는 구조체에서 공유하는 메소드를 구현하기 위해 트레이트를 먼저 선언해야 합니다. 트레이트에서는 공유할 메소드의 원형을 선언합니다.
trait Greet {
    fn say_hello(&self) {}
}이렇게 선언하면, say_hello는 아무것도 실행하지 않는 빈 함수이기 때문에, 실제 내용을 각 구조체의 메소드에서 구현해야 합니다. 혹은 say_hello의 기본 구현형을 트레이트를 선언할 때 정의할 수도 있습니다.
이제 파이썬과 동일하게 러스트 코드를 작성해 보겠습니다.
struct Person {
    name: String,
    age: i32,
    alive: bool,
}
impl Person {
    fn new(name: &str, age: i32) -> Person {
        Person {
            name: String::from(name),
            age: age,
            alive: true
        }
    }
    fn get_older(&mut self, year: i32) {
        self.age += year;
    }
}
impl Greet for Person {}
struct Student {
    name: String,
    age: i32,
    alive: bool,
    major: String,
}
impl Student {
    fn new(name: &str, age: i32, major: &str) -> Student {
        Student {
            name: String::from(name),
            age: age,
            alive: true,
            major: String::from(major),
        }
    }
}
impl Greet for Student {
    fn say_hello(&self) {
        println!("Hello, I am {} and I am studying {}", self.name, self.major)
    }
}이제 메인 함수에서 Person과 Student 구조체의 인스턴스를 만들고 say_hello 메소드를 각각 호출해 보겠습니다.
fn main() { let mut person = Person::new("John", 20); person.say_hello(); // 🫢 person.get_older(1); println!("{} is now {} years old", person.name, person.age); let student = Student::new("Jane", 20, "Computer Science"); student.say_hello(); }
실행 결과
John is now 21 years old
Hello, I am Jane and I am studying Computer Science
person.say_hello()는 trait Greet의 메소드를 그대로 사용하기 때문에 아무것도 출력되지 않는 것을 알 수 있습니다.
다시 트레이트 선언으로 돌아가보면 say_hello 함수는 파라미터로 &self 를 받고 있지만, 트레이트에 정의되는 함수는 인스턴스 프로퍼티에 접근할 수 없습니다. 만일 여기서 다음과 같이 함수의 원형을 수정하고 컴파일해보면 에러가 발생합니다.
trait Greet {
    fn say_hello(&self) {
        println!("Hello, Rustacean!");
    }
}파생(Derive)
컴파일러는 #[derive] 트레이트을 통해 일부 특성에 대한 기본 구현을 제공할 수 있습니다. 보다 복잡한 동작이 필요한 경우 이러한 특성은 직접 구현할 수 있습니다.
다음은 파생 가능한 트레이트 목록입니다:
- 비교: Eq,PartialEq,Ord,PartialOrd.
- Clone, 복사본을 통해- &T에서- T를 생성합니다.
- Copy, '이동 시맨틱' 대신 '복사 시맨틱' 타입을 제공합니다.
- Hash,- &T에서 해시를 계산합니다.
- Default, 데이터 타입의 빈 인스턴스를 생성합니다.
- {:?}포매터를 사용하여 값의 형식을 지정하려면- Debug.
struct Rectangle { width: u32, height: u32, } fn main() { let rect1 = Rectangle { width: 30, height: 50, }; println!("rect1 is {:?}", rect1); // 🤯 }
error[E0277]: `Rectangle` doesn't implement `Debug`
  --> src/main.rs:12:31
   |
12 |     println!("rect1 is {:?}", rect1); // 🤯
   |                               ^^^^^ `Rectangle` cannot be formatted using `{:?}`
   |
   = help: the trait `Debug` is not implemented for `Rectangle`
   = note: add `#[derive(Debug)]` to `Rectangle` or manually `impl Debug for Rectangle`
#[derive(Debug)] struct Rectangle { width: u32, height: u32, } fn main() { let rect1 = Rectangle { width: 30, height: 50, }; println!("rect1 is {:?}", rect1); }