← Rust 강의 목록으로
🦀
타입 시스템
타입 시스템 · 선수: 13강

14. 트레잇 (정의·구현·기본 메서드·trait object)

트레잇(trait) 은 "이 타입이 이런 동작을 한다" 는 약속입니다. Java/Kotlin 의 interface, Haskell 의 typeclass 와 비슷한 역할. 기본 메서드 구현, 트레잇 바운드, dyn Trait 의 동적 디스패치까지 한 번에 짚습니다. 표준 라이브러리의 Display·Debug·Iterator 같은 트레잇이 어떻게 다양한 메서드 체이닝을 가능하게 하는지도 살펴봅니다.

Rusttraitinterfacedyngenericspolymorphism
소요 시간
약 2시간
난이도
📊 중급
선수 조건
🎯 13강
결과물
트레잇(trait) 은 "이 타입이 이런 동작을 한다" 는 약속입니다. Java/Kotlin 의 interface, Haskell 의 typeclass 와 비슷한 역할. 기본 메서드 구현, 트레잇 바운드, dyn Trait 의 동적 디스패치까지 한 번에 짚습니다. 표준 라이브러리의 Display·Debug·Iterator 같은 트레잇이 어떻게 다양한 메서드 체이닝을 가능하게 하는지도 살펴봅니다.

이 강의에서 배우는 것

  • 1trait 을 정의하고 여러 타입에 impl 한다
  • 2기본 메서드 구현으로 공통 동작을 한 곳에 둔다
  • 3트레잇 바운드로 제네릭 함수의 동작을 제한한다
  • 4dyn Trait vs impl Trait 의 디스패치 차이를 안다
  • 5표준 라이브러리의 Debug / Display / Clone / Default 등을 derive 또는 직접 구현한다

소개

Java 의 interface 가 익숙하다면 trait 는 그것의 강화판입니다 — interface 가 약속만 하고 끝나는 것과 달리, trait 은 기본 구현·연관 타입·연관 상수까지 묶을 수 있어 표준 라이브러리의 풍부한 메서드 체이닝(Iterator 등) 이 가능합니다.

핵심 개념

1) trait 정의와 구현

rust
trait Greet {
    fn hello(&self) -> String;
    // 기본 메서드 — 호출하는 타입에서 override 안 해도 됨
    fn shout(&self) -> String {
        format!("{}!!!", self.hello())
    }
}

struct Korean;
impl Greet for Korean {
    fn hello(&self) -> String { "안녕".into() }
}

2) 트레잇 바운드 — 제네릭 함수 제한

rust
fn print_hello<T: Greet>(t: &T) {
    println!("{}", t.hello());
}
// 같은 의미의 where 절
fn print_hello2<T>(t: &T) where T: Greet { /* ... */ }

3) impl Trait — 익명 반환 타입

함수 반환 타입에 `impl Trait` 을 쓰면 "이 트레잇을 구현하는 어떤 타입" 을 반환한다는 의미. 구체 타입은 컴파일러가 추론.

4) dyn Trait — 동적 디스패치

관점impl Trait / 제네릭dyn Trait
디스패치정적 (compile-time)동적 (runtime vtable)
성능빠름 (인라인 가능)함수 호출 비용 약간
다양한 타입 컬렉션✗ (모든 원소 같은 타입)✓ (Vec<Box<dyn Trait>>)
바이너리 크기단형화로 커질 수 있음더 작음

5) 흔히 쓰는 표준 트레잇

  • **Debug** — `{:?}` 포매팅, `#[derive(Debug)]` 으로 자동 생성
  • **Display** — `{}` 포매팅, 직접 구현
  • **Clone / Copy** — 복제 / 비트 복사
  • **Default** — `Type::default()` 생성
  • **PartialEq / Eq** — `==` 비교
  • **Iterator** — for 루프와 .map/.filter 등의 체이닝 가능하게 함

핵심 예제

trait 정의 + 여러 타입에 구현:

rust
trait Area {
    fn area(&self) -> f64;
    fn report(&self) {
        println!("area = {:.2}", self.area());
    }
}

struct Circle { r: f64 }
struct Square { s: f64 }

impl Area for Circle {
    fn area(&self) -> f64 { std::f64::consts::PI * self.r * self.r }
}
impl Area for Square {
    fn area(&self) -> f64 { self.s * self.s }
}

fn main() {
    Circle { r: 2.0 }.report();   // area = 12.57
    Square { s: 3.0 }.report();   // area = 9.00
}

dyn Trait 로 다형성 컬렉션:

rust
fn main() {
    let shapes: Vec<Box<dyn Area>> = vec![
        Box::new(Circle { r: 1.0 }),
        Box::new(Square { s: 2.0 }),
    ];
    for s in &shapes { s.report(); }
}

derive 매크로로 표준 트레잇 자동 구현:

rust
#[derive(Debug, Clone, PartialEq, Default)]
struct Point { x: f64, y: f64 }

fn main() {
    let p1 = Point::default();              // (0.0, 0.0)
    let p2 = p1.clone();
    println!("{:?} == {:?} ? {}", p1, p2, p1 == p2);
}

자주 하는 실수

Q. impl Trait 와 dyn Trait 중 뭘 써야 하나요?

A. **다양한 구체 타입을 한 컬렉션에 담아야 하면 dyn**, 그 외엔 정적 디스패치(impl Trait 또는 제네릭) 가 빠르고 인라인이 됩니다. 라이브러리 API 의 반환 타입을 깔끔하게 숨길 때도 impl Trait 가 유용.

Q. orphan rule 이 뭔가요?

A. **타입 또는 trait 중 하나는 자신의 크레잇에서 정의된 것이어야** 외부 타입에 외부 trait 을 impl 할 수 있다는 규칙. "Vec<i32> 에 Display 를 직접 impl" 같은 건 금지 — 새 wrapper 타입을 만들어야 합니다.

Q. Display 를 derive 할 수 있나요?

A. 표준은 못 합니다 (포맷이 너무 자유로워서). `impl fmt::Display for X` 를 직접 작성하거나 외부 크레잇(예: derive_more) 사용.

정리

  • trait 은 동작 약속 + 기본 구현까지 포함
  • 트레잇 바운드로 제네릭의 동작을 컴파일 타임에 제한
  • impl Trait = 정적 디스패치, dyn Trait = 동적 디스패치
  • Debug / Clone / Default / PartialEq 등 자주 쓰는 트레잇은 derive 로 자동 생성

과제

  1. trait Summary { fn summarize(&self) -> String } 를 정의하고 Article / Tweet 두 타입에 구현
  2. 기본 메서드를 가진 trait Animal { fn name(); fn greet() { ... } } 작성 후 사용
  3. Box<dyn Animal> Vec 로 여러 종류 동물을 담아 .greet() 호출하기
예제 코드 / 강의 자료

전체 강의 자료와 예제 코드는 GitHub에서 자유롭게 받아볼 수 있습니다.

GitHub에서 보기 ↗