Rust의 Trait
Rust에서 trait는 "이 타입은 이런 동작을 할 수 있다"는 걸 정의하는 인터페이스다. C++의 추상 클래스, Java의 interface와 비슷하지만 상속 없이 다형성을 만들고, 컴파일 타임 제로코스트 추상화를 제공한다.
핵심 아이디어
타입은 데이터만 담고, 동작은 trait로 분리한다.
trait Greet {
fn hello(&self) -> String;
}
struct Korean;
impl Greet for Korean {
fn hello(&self) -> String { "안녕하세요".to_string() }
}
C++ 추상 클래스와의 차이
| | C++ abstract class | Rust trait | |---|---|---| | 다형성 방식 | 상속 (is-a) | 구현 (has-a behavior) | | 디스패치 기본값 | 동적 (virtual) | 정적 (monomorphization) | | 나중에 붙이기 | 불가 | 가능 (외부 타입에도 impl) |
C++은 virtual 함수면 자동으로 vtable — Rust는 정적/동적을 명시적으로 선택해야 한다.
Trait Bound (제네릭과의 결합)
fn greet_all<T: Greet>(item: &T) {
println!("{}", item.hello());
}
T: Greet는 컴파일 타임 조건. 타입별로 별도 코드가 생성되므로 (monomorphization) 런타임 오버헤드가 없다. C++ template과 같은 방식.
동적 디스패치: dyn Trait
런타임에 타입을 결정해야 하면 dyn Trait 사용. 이때 vtable을 통한 동적 디스패치 발생.
fn greet_dynamic(item: &dyn Greet) {
println!("{}", item.hello());
}
dyn Trait는 fat pointer — 데이터 포인터 + vtable 포인터 두 개를 들고 다닌다.
Default 구현
trait 안에 기본 구현을 넣을 수 있다. 오버라이드하지 않으면 기본값이 쓰인다.
trait Greet {
fn hello(&self) -> String;
fn shout(&self) -> String {
self.hello().to_uppercase() // 기본 구현
}
}
마커 Trait
동작이 없이 "이 타입은 이런 성질을 가진다"는 표시만 하는 trait도 있다.
Send— 스레드 간 소유권 이동 가능Sync— 스레드 간 참조 공유 가능Sized— 컴파일 타임에 크기를 알 수 있음
이들은 컴파일러가 자동으로 구현하거나 (derive), 명시적으로 구현할 수 있다.