트레이트(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); }