Rustのトレイトのメモ。
トレイトは、共通の振る舞い(メソッド)を定義するもの。
トレイトはtraitブロックで定義する。
〔可視性〕trait トレイト名〔<型引数1, 型引数2, …>〕〔: 継承元トレイト〔<型引数, …>〕〕 { fn メソッド名(&self, 引数名1 : 型1, 引数名2 : 型2, …) -> 戻り型; 〜 }
トレイトは別のトレイトを継承することが出来る。
traitブロックで定義するメソッドは、処理本体を書かない。
メソッドには可視性は付けられず、pub相当になる。[2025-07-05]
トレイトは(構造体や列挙型でメソッドを定義するときと同様に)implブロックで実装する。
impl トレイト名 for 構造体または列挙名 { fn メソッド名(&self, 引数名1 : 型1, 引数名2 : 型2, …) -> 戻り型 { 〜 } 〜 }
implに指定するトレイトとforに指定する構造体・列挙型は、どちらかが自分のクレートで定義したものである必要がある。[2025-07-05]
両方とも自分のクレートでない場合はコンパイルエラーになる。
トレイトがジェネリクスを持っているとき、ジェネリクスに指定する部分のみが自分のクレートの型である場合も駄目。
(例えば「impl From<自分の構造体> for 他の構造体」は、Fromも他構造体も自分のクレートではないので、実装できない)
implブロックでは、trait文で定義されたメソッドを実装する。
トレイトを実装したfnは、可視性は何も付けないが、pub相当になる。(それ以外には出来ない)[2025-07-05]
トレイトを実装したブロックでは、トレイトで宣言されているメソッド以外は定義できない。[2025-07-05]
(サブルーチン的なメソッドを用意したい場合は、別のimplブロックで定義する。そのメソッドは、selfのメソッドとして呼び出すことが出来る)
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();
トレイトのメソッドを呼び出す際は、そのトレイトもuseしておく必要がある。[2025-07-05]
異なるトレイトが同名のメソッドを持つこともあるので、どれを呼び出すか明示する為だろう。
use crate::{MyStruct, MyTrait}; fn sub() { 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トレイト)