learn

Rust의 Trait

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), 명시적으로 구현할 수 있다.

관련 개념