導入
トレイトは「この型は、こういう振る舞い(メソッド)ができる」という約束を定義するものです。「面積を計算できる」「文字列で表現できる」といった能力を型に持たせ、異なる型を同じインターフェースで扱えるようにします。オブジェクト指向の「インターフェース」に相当します。
説明
// トレイト定義:「要約できる」という振る舞い
trait Summary {
fn summarize(&self) -> String;
}
struct Article {
title: String,
body: String,
}
struct Tweet {
username: String,
text: String,
}
// Article に Summary を実装
impl Summary for Article {
fn summarize(&self) -> String {
format!("{}(記事)", self.title)
}
}
// Tweet に Summary を実装
impl Summary for Tweet {
fn summarize(&self) -> String {
format!("@{}: {}", self.username, self.text)
}
}
fn main() {
let a = Article { title: String::from("Rust入門"), body: String::from("...") };
let t = Tweet { username: String::from("ferris"), text: String::from("🦀") };
println!("{}", a.summarize()); // Rust入門(記事)
println!("{}", t.summarize()); // @ferris: 🦀
}
trait Summary { fn summarize(&self) -> String; }… 「summarizeというメソッドを持つ」という約束(本体は書かず、シグネチャだけ)。impl Summary for Article…Articleに対して、その約束を具体的に実装します。Tweetにも別の実装を与えられます。- 結果、型は違うのに「
summarize()できる」という共通の能力を持ちます。
第7章で使った #[derive(Debug)] も、実は「Debug トレイトを自動で実装してくれ」という指示でした。Clone(第4章)や Copy(第3章)もトレイトです。これまで暗黙に使ってきた機能の多くが、トレイトで表現されていたわけです。
トレイトには既定の実装も書けます。
trait Summary {
fn summarize(&self) -> String {
String::from("(要約なし)") // 実装しなければこれが使われる
}
}
こうしておくと、impl Summary for X {} と空で書くだけで既定の振る舞いが手に入ります。
試すには
新しい構造体 Book を作り impl Summary for Book を書けば、Book も summarize() できるようになります。トレイトは「型の集合に共通の能力を後付けする」仕組みだと捉えると、次のレッスンの使い道が見えてきます。