trait / impl Trait for 型
| 対応: | Rust 1.0(2015) |
|---|
Rustのトレイト(trait)は型が実装すべきメソッドを定義するインターフェースです。『trait』キーワードで定義し、『impl Trait for 型』で実装します。デフォルト実装も提供できます。
構文
// トレイトの定義
trait Greet {
// 必須メソッド: 実装側で定義する
fn name(&self) -> &str;
// デフォルト実装: そのまま使えるが、オーバーライドも可能
fn greet(&self) {
println!("Hello, {}!", self.name());
}
}
struct English {
name: String,
}
// トレイトの実装
impl Greet for English {
fn name(&self) -> &str {
&self.name
}
// greet() はデフォルト実装を使う
}
// 引数にトレイトを受け取る(impl Trait 構文)
fn introduce(item: &impl Greet) {
item.greet();
}
構文・概念一覧
| 構文・概念 | 概要 |
|---|---|
| trait Foo { fn method(&self); } | トレイトを定義します。 |
| fn method(&self) { ... } | トレイト内のデフォルト実装です。 |
| impl Foo for 型 { ... } | 型にトレイトを実装します。 |
| fn func(x: &impl Foo) | impl Trait構文: トレイトを実装した型を引数に取ります。 |
| fn func<T: Foo>(x: &T) | トレイト境界構文: 同上(ジェネリクス版)。 |
| fn func() -> impl Foo | トレイトを実装した型を戻り値にします。 |
| dyn Foo | 動的ディスパッチ: トレイトオブジェクトとして使います。 |
| Box<dyn Foo> | ヒープに置いたトレイトオブジェクトです(型消去)。 |
サンプルコード
trait_impl.rs
trait Area {
fn area(&self) -> f64;
// デフォルト実装: 面積を整形して表示する
fn describe(&self) {
println!("面積: {:.2}", self.area());
}
}
trait Perimeter {
fn perimeter(&self) -> f64;
}
struct Circle {
radius: f64,
}
struct Rectangle {
width: f64,
height: f64,
}
impl Area for Circle {
fn area(&self) -> f64 {
std::f64::consts::PI * self.radius * self.radius
}
}
impl Area for Rectangle {
fn area(&self) -> f64 {
self.width * self.height
}
// describe() はデフォルト実装を使う
}
impl Perimeter for Rectangle {
fn perimeter(&self) -> f64 {
2.0 * (self.width + self.height)
}
}
// impl Trait 構文: Areaトレイトを実装した型を引数に取る
fn print_area(shape: &impl Area) {
shape.describe();
}
// 動的ディスパッチ: 異なる型のトレイトオブジェクトをまとめて扱う
fn largest_area(shapes: &[Box<dyn Area>]) -> f64 {
shapes.iter().map(|s| s.area()).fold(0.0_f64, f64::max)
}
fn main() {
let c = Circle { radius: 3.0 };
let r = Rectangle { width: 4.0, height: 6.0 };
// impl Trait で多態的に呼び出す
print_area(&c);
print_area(&r);
// Box<dyn Trait> で異なる型をVecに混在させる
let shapes: Vec<Box<dyn Area>> = vec![
Box::new(Circle { radius: 1.0 }),
Box::new(Rectangle { width: 3.0, height: 4.0 }),
Box::new(Circle { radius: 5.0 }),
];
for shape in &shapes {
shape.describe();
}
println!("最大面積: {:.2}", largest_area(&shapes));
}
rustc trait_impl.rs ./trait_impl 面積: 28.27 面積: 24.00 面積: 3.14 面積: 12.00 面積: 78.54 最大面積: 78.54
複数トレイト境界と where 句
複数のトレイト境界を指定するには『+』を使います。境界が複雑な場合は『where』句でまとめて書くと読みやすくなります。トレイト境界が多くなり関数シグネチャが読みにくくなった場合は、where 句を使うと読みやすくなります。
sample_trait_bounds.rs
use std::fmt::{Debug, Display};
// impl Trait 構文: 境界が少ない場合はシンプルに書けます
fn print_info(item: &(impl Display + Debug)) {
println!("Display: {}", item);
println!("Debug: {:?}", item);
}
// where 句: 境界が複雑な場合に読みやすくなります
fn compare_and_print(a: T, b: U)
where
T: Display + PartialOrd,
U: Display,
{
println!("a = {}, b = {}", a, b);
}
// トレイト境界を使ったジェネリクス関数(実践パターン)
// 任意の型のVecから最大値を求めます
fn find_max(list: &[T]) -> Option<&T> {
if list.is_empty() {
return None;
}
let mut max = &list[0];
for item in list {
if item > max {
max = item;
}
}
Some(max)
}
// 条件付きメソッド実装: T が Display を実装している場合のみメソッドを追加します
struct Wrapper(T);
impl Wrapper {
fn display(&self) {
println!("Wrapper: {}", self.0);
}
}
fn main() {
print_info(&"Kiryu Kazuma");
compare_and_print(100, "Majima Goro");
let powers = vec![95, 92, 88, 76, 80];
if let Some(max) = find_max(&powers) {
println!("最大値: {}", max);
}
let w = Wrapper("Dragon of Dojima");
w.display();
// Wrapper(42) にも display() が使えます
let w2 = Wrapper(42);
w2.display();
}
rustc sample_trait_bounds.rs ./sample_trait_bounds Display: Kiryu Kazuma Debug: "Kiryu Kazuma" a = 100, b = Majima Goro 最大値: 95 Wrapper: Dragon of Dojima Wrapper: 42
よくあるミス1: 孤児ルール違反
外部型に外部トレイトを実装しようとするとコンパイルエラーになります。Display も Vec も外部クレートで定義されているため不可です。
use std::fmt::Display; // impl Display for Vec{ ... } // コンパイルエラー: Display も Vec も外部クレートで定義されているため不可
修正: ニュータイプパターンを使ってラップします。
use std::fmt::Display; struct MyVec(Vec); impl Display for MyVec { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "[{}]", self.0.join(", ")) } }
よくあるミス2: dyn Traitの直接返却
dyn Trait を直接返そうとするとサイズが不定なためコンパイルエラーになります。
trait Greet {
fn greet(&self) -> String;
}
// fn make_greeter() -> dyn Greet { ... }
// コンパイルエラー: dyn Trait はサイズが不定なので直接返せません
修正1: impl Trait を使う(静的ディスパッチ)。修正2: Box<dyn Trait> を使う(動的ディスパッチ・複数の型を返したい場合)。
sample_trait_mistakes.rs
trait Greet {
fn greet(&self) -> String;
}
struct Character { name: String }
impl Greet for Character {
fn greet(&self) -> String { format!("俺は{}だ", self.name) }
}
// impl Trait を使う(静的ディスパッチ)
fn make_greeter_impl() -> impl Greet {
Character { name: "Kiryu".to_string() }
}
// Box を使う(動的ディスパッチ・複数の型を返したい場合)
fn make_greeter_dyn(use_kiryu: bool) -> Box {
if use_kiryu {
Box::new(Character { name: "Kiryu".to_string() })
} else {
Box::new(Character { name: "Majima".to_string() })
}
}
fn main() {
let v = MyVec(vec!["Kiryu".to_string(), "Majima".to_string()]);
println!("{}", v);
let g1 = make_greeter_impl();
println!("{}", g1.greet());
let g2 = make_greeter_dyn(false);
println!("{}", g2.greet());
}
rustc sample_trait_mistakes.rs ./sample_trait_mistakes [Kiryu, Majima] 俺はKiryuだ 俺はMajimaだ
概要
Rustのトレイトは、「孤児ルール(orphan rule)」により自分が定義した型か自分が定義したトレイトの少なくとも一方を持つ場合のみ実装できます。外部クレートの型に外部クレートのトレイトを実装することはできません。
『impl Trait』はコンパイル時に具体的な型が決まる静的ディスパッチ(モノモーフィズム)です。『dyn Trait』は実行時に型が決まる動的ディスパッチです。静的ディスパッチの方が高速ですが、動的ディスパッチは異なる型をコレクションにまとめられます。
DisplayトレイトとDebugトレイトは『Display トレイト / Debug トレイト』を参照してください。
記事の間違いや著作権の侵害等ございましたらお手数ですがこちらまでご連絡頂ければ幸いです。