Rustのトレイトのメモ。
トレイトは、共通の振る舞い(メソッド)を定義するもの。
トレイトはtraitブロックで定義する。
〔可視性〕trait トレイト名〔<型引数1, 型引数2, …>〕〔: 継承元トレイト〔<型引数, …>〕〕 { fn メソッド名(&self, 引数名1 : 型1, 引数名2 : 型2, …) -> 戻り型; 〜 }
トレイトは別のトレイトを継承することが出来る。
traitブロックで定義するメソッドは、処理本体を書かない。
トレイトは(構造体や列挙型でメソッドを定義するときと同様に)implブロックで実装する。
impl トレイト名 for 構造体または列挙名 { fn メソッド名(&self, 引数名1 : 型1, 引数名2 : 型2, …) -> 戻り型 { 〜 } 〜 }
implブロックでは、trait文で定義されたメソッドを実装する。
Javaでは、インターフェースをクラスで実装する構文は「class クラス名 implements
インターフェース名」(実装されるクラス名が左側、インターフェース名が右側)である。
Rustでは「impl トレイト名 for 構造体名」(実装される構造体名が右側でトレイト名が左側)で、Javaとは逆。
Javaに慣れた身ではちょっと混乱する^^;
Javaだと、インターフェースで宣言されたメソッドを実装するときは@Overrideというアノテーションを付けるが、
Rustのトレイトでは、そういった印は付けない。
trait MyTrait { fn method(&self); } struct MyStruct { value: i32, } impl MyTrait for MyStruct { fn method(&self) { println!("value={}", self.value); } }
let s = MyStruct { value: 123 }; s.method();
Rustでは、ひとつのimplブロックの中でメソッドをオーバーロードする(同名のメソッドを複数定義する)ことは出来ない。[2024-09-19]
しかし、異なるトレイト同士では同名のメソッドがあっても不思議は無く、ひとつの構造体や列挙型が両方のトレイトを実装することが出来る。
結果として、オーバーロードのようなことが実現できる。
struct MyStruct { value: i32, } trait MyTrait1 { fn method(&self); } trait MyTrait2 { fn method(&self); } impl MyTrait1 for MyStruct { fn method(&self){ println!("impl MyTrait1"); } } impl MyTrait2 for MyStruct { fn method(&self){ println!("impl MyTrait2"); } }
このようにして、「引数の型が異なる同名メソッド」も「引数の型が同一で戻り型が異なる同名メソッド」も定義できる。
(Javaの場合、引数の型が同一で戻り型のみが異なる同名メソッドは定義できない)
しかしこの場合、呼び出す側でどちらのメソッドを呼ぶかを決定する必要がある。
let s = MyStruct { value: 123 };
× s.method(); // MyTrait1の実装とMyTrait2の実装のどちらのmethodを呼び出せばいいか分からないのでコンパイルエラー
どちらのトレイトなのかを明示して呼び出す。
let s = MyStruct { value: 123 }; MyTrait1::method(&s); MyTrait2::method(&s);
また、ジェネリクス付きのトレイトを定義して、メソッドの引数や戻り値の型に型引数を使うようにしておくと、トレイトを実装する側で型引数ごとに個別の実装をすることが出来る。
そのメソッドを呼び出す際に、合致する型のメソッドが自動的に選ばれる。
(合致する型のメソッドが無い場合はコンパイルエラーになる)
→引数の型が異なるオーバーロードの例(Fromトレイト)
→戻り値の型が異なるオーバーロードの例(Intoトレイト)