到達点は、実装に依存せず操作の約束を表すことです。前提は第06・07回です。
操作の共通部分を名前にする
トレイトは、型が提供する操作などの約束です。ジェネリクスにトレイト境界を付けると、どの型でもよいのではなく、必要な操作を持つ型だけを受け入れます。
trait Described {
fn describe(&self) -> String;
}
struct Lesson { title: String }
struct Review { count: usize }
impl Described for Lesson {
fn describe(&self) -> String { format!("学習: {}", self.title) }
}
impl Described for Review {
fn describe(&self) -> String { format!("復習: {}件", self.count) }
}
fn show<T: Described>(value: &T) {
println!("{}", value.describe());
}
fn main() {
let lesson = Lesson { title: "trait".to_owned() };
show(&lesson);
let items: Vec<Box<dyn Described>> = vec![
Box::new(lesson), Box::new(Review { count: 3 })];
for item in items { println!("{}", item.describe()); }
}
src/main.rsで学習の表示が二回、復習3件が一回出ます。ジェネリックなshowは具体型に応じたコードとして使われます。一方、dyn Describedは異なる具体型を共通の参照先として扱い、実行時に適切な操作を選ぶトレイトオブジェクトです。
どちらを選ぶか
コンパイル時に型が決まるならジェネリクスが自然です。実行時に異なる種類を同じ入れ物へ混ぜたいなら、dynやenumが候補になります。dynには、トレイトが動的呼び出し可能であるための制約があります。単にエラーを消すためにBoxを増やすのではなく、必要な表現から選びます。
関連型は、ある実装に対応する型をトレイト内で表す方法です。IteratorのItemが代表例です。また、外部トレイトを外部型へ自由に実装できるわけではありません。必要なら自分のラッパー型を作るnewtypeパターンを使います。
練習と解答
練習:二種類しかなく、将来追加時に必ず全分岐を見直したい場合はdynとenumのどちらが候補でしょうか。
解答:enumならmatchの網羅性を使えます。型を外部から追加できる拡張性を重視する場合はトレイトが候補です。どちらかが常に優れているのではなく、変更の仕方が違います。
公式資料
トレイトとトレイトオブジェクトを参照できます。
Rust全20回の目次 | 前の回 | 次の回