홈강좌Rust 입문·중급 47편 중 33편째
Rust 트레이트 — 여러 타입이 같은 동작을 하겠다고 약속한다
Rust 트레이트(trait)는 "이런 메서드를 갖고 있다" 는 약속이다. trait 으로 메서드 목록을 정하고, impl 트레이트 for 타입 으로 타입마다 채운다. 약속한 메서드를 빠뜨리면 E0046 이 난다. 기본 구현을 둘 수 있고, &impl 트레이트 로 그 약속을 지키는 타입이면 무엇이든 받는 함수를 만든다.
01 trait 으로 약속하고 impl 로 지킨다
블로그 글과 짧은 글은 서로 다른 구조체다. 둘 다 "한 줄 요약" 을 만들 수 있게 해 보자.
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());
} 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 은 했는데 메서드를 안 채워 보자.
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);
}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- 무엇 — 트레이트의 항목을 다 구현하지 않았다. 빠진 것은
summarize다. - 어디 — 10번째 줄의
impl Summary for Tweet {}. 2번째 줄의 트레이트 정의도 함께 짚는다. - 고치는 법 —
help:는 없지만 무엇 줄이 빠진 이름을 댄다.impl안에summarize를 쓴다.
"지킨다" 고 해 놓고 안 지키면 컴파일러가 막는다. 그래서 트레이트를 지키는 타입이면 그 메서드가 반드시 있다고 믿을 수 있다.
03 실습 — 기본 구현과 트레이트를 받는 함수
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() });
} 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 에 넘겨 보자. 다음 편은 입문 구간의 마지막, 지금까지 배운 것으로 작은 프로그램을 처음부터 만든다.