hyunsu blog

홈강좌Rust 입문·중급 47편 중 33편째

Rust 트레이트 — 여러 타입이 같은 동작을 하겠다고 약속한다

목차 · 3
  1. 01trait 으로 약속하고 impl 로 지킨다
  2. 02약속을 빠뜨리면 E0046
  3. 03실습 — 기본 구현과 트레이트를 받는 함수

Rust 트레이트(trait)는 "이런 메서드를 갖고 있다" 는 약속이다. trait 으로 메서드 목록을 정하고, impl 트레이트 for 타입 으로 타입마다 채운다. 약속한 메서드를 빠뜨리면 E0046 이 난다. 기본 구현을 둘 수 있고, &impl 트레이트 로 그 약속을 지키는 타입이면 무엇이든 받는 함수를 만든다.

01 trait 으로 약속하고 impl 로 지킨다

블로그 글과 짧은 글은 서로 다른 구조체다. 둘 다 "한 줄 요약" 을 만들 수 있게 해 보자.

rust
trait Summary {
    fn summarize(&self) -> String;
}

struct Post {
    title: String,
    author: String,
}

struct Tweet {
    user: String,
    text: String,
}

impl Summary for Post {
    fn summarize(&self) -> String {
        format!("{} by {}", self.title, self.author)
    }
}

impl Summary for Tweet {
    fn summarize(&self) -> String {
        format!("@{}: {}", self.user, self.text)
    }
}

fn main() {
    let post = Post { title: "Learning Rust".to_string(), author: "Ferris".to_string() };
    let tweet = Tweet { user: "ferris".to_string(), text: "hello!".to_string() };
    println!("{}", post.summarize());
    println!("{}", tweet.summarize());
}
text
   Compiling summary v0.1.0 (~/projects/summary)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.19s
     Running `target/debug/summary`
Learning Rust by Ferris
@ferris: hello!
  • trait Summary { fn summarize(&self) -> String; } 은 "Summary 를 지키는 타입은 summarize 메서드를 가진다" 는 약속이다. 몸체 없이 첫 줄에 세미콜론만 찍는다.
  • impl Summary for Post { … } 는 "Post 는 Summary 를 지킨다" 이고, 안에 실제 몸체를 쓴다. 23편의 impl Post 와 달리 이름 사이에 트레이트 for 가 들어간다.
  • Tweet 도 같은 약속을 자기 방식으로 지킨다. 부르는 쪽은 post.summarize() 처럼 똑같이 부른다.

format! 은 20편에서 본, 문자열을 만들어 돌려주는 매크로다.

02 약속을 빠뜨리면 E0046

impl 은 했는데 메서드를 안 채워 보자.

rust
trait Summary {
    fn summarize(&self) -> String;
}

struct Tweet {
    user: String,
    text: String,
}

impl Summary for Tweet {}

fn main() {
    let tweet = Tweet { user: "ferris".to_string(), text: "hello!".to_string() };
    println!("{} {}", tweet.user, tweet.text);
}
text
error[E0046]: not all trait items implemented, missing: `summarize`
  --> src/main.rs:10:1
   |
 2 |     fn summarize(&self) -> String;
   |     ------------------------------ `summarize` from trait
...
10 | impl Summary for Tweet {}
   | ^^^^^^^^^^^^^^^^^^^^^^ missing `summarize` in implementation
  1. 무엇 — 트레이트의 항목을 다 구현하지 않았다. 빠진 것은 summarize 다.
  2. 어디 — 10번째 줄의 impl Summary for Tweet {}. 2번째 줄의 트레이트 정의도 함께 짚는다.
  3. 고치는 법 — help: 는 없지만 무엇 줄이 빠진 이름을 댄다. impl 안에 summarize 를 쓴다.

"지킨다" 고 해 놓고 안 지키면 컴파일러가 막는다. 그래서 트레이트를 지키는 타입이면 그 메서드가 반드시 있다고 믿을 수 있다.

03 실습 — 기본 구현과 트레이트를 받는 함수

rust
trait Summary {
    fn summarize(&self) -> String;

    fn headline(&self) -> String {
        format!("[NEW] {}", self.summarize())
    }
}

struct Post {
    title: String,
}

struct Tweet {
    text: String,
}

impl Summary for Post {
    fn summarize(&self) -> String {
        self.title.clone()
    }
}

impl Summary for Tweet {
    fn summarize(&self) -> String {
        self.text.clone()
    }

    fn headline(&self) -> String {
        format!("tweet: {}", self.text)
    }
}

fn announce(item: &impl Summary) {
    println!("{}", item.headline());
}

fn main() {
    announce(&Post { title: "Learning Rust".to_string() });
    announce(&Tweet { text: "hello!".to_string() });
}
text
   Compiling summary v0.1.0 (~/projects/summary)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.17s
     Running `target/debug/summary`
[NEW] Learning Rust
tweet: hello!
  • headline 은 트레이트 안에 몸체까지 썼다. 이것이 기본 구현이다. Post 는 따로 쓰지 않았으니 기본 구현([NEW] …)을 쓴다.
  • Tweet 은 headline 을 직접 다시 써서 기본 구현을 덮어썼다(tweet: …).
  • 기본 구현 안에서 self.summarize() 를 부를 수 있다. 각 타입이 채운 summarize 가 불린다.
  • fn announce(item: &impl Summary) 는 "Summary 를 지키는 타입이면 무엇이든 빌려 받는다" 는 뜻이다. Post 도 Tweet 도 넘길 수 있다.

32편의 경계 T: PartialOrd 도 같은 이야기다. PartialOrd 는 표준 라이브러리의 트레이트이고, "> 로 비교할 수 있다" 는 약속이다. 22편에서 #[derive(Debug)] 로 붙인 것도 Debug 트레이트의 구현을 컴파일러가 대신 써 준 것이다. fn announce<T: Summary>(item: &T) 로 써도 같은 뜻이다.

Summary 를 지키는 세 번째 구조체(예: 사진 Photo { caption: String })를 만들어 announce 에 넘겨 보자. 다음 편은 입문 구간의 마지막, 지금까지 배운 것으로 작은 프로그램을 처음부터 만든다.