型消去の話で出てきたポケモンの例題を理解する #tryswiftconfで出てきた例をRustに移植してみます。
trait Pokemon
Swiftのprotocol
に相当するものとして、Rustにはtrait
があります。
trait Pokemon {
type PokemonType;
fn attack(&mut self, move_: Self::PokemonType);
}
関連型 (Associated Types) が使える点を含めて、Swiftの例とほとんど一緒ですね! ですが、以下の相違点があります。
-
protocol
→trait
-
typealias
→type
-
func
→fn
-
Void
→()
ですが、関数の返り値の型では省略できます。 - Rustでは行末にセミコロンが要ります。
- Rustではレシーバー
&mut self
を明示的に書きます。 - Rustでは
move
が予約語なので、代わりにmove_
としています。 - Rustでは
PokemonType
の名前空間を指定するためにキーワードSelf
が要ります。
struct Pikachu
重要な点として、Rustにはクラスが存在せず、データ定義とメソッド実装は必ず別に書かれます。
struct Thunder;
struct Pikachu;
impl Pokemon for Pikachu {
type PokemonType = Thunder;
fn attack(&mut self, _move_: Thunder) {
println!("Pikachu attacks ⚡️")
}
}
struct Thunder;
のように書くと、フィールドを持たない構造体が定義できます。このような構造体を_unit-like structs_と呼びます。(unitは()
の呼び名です。)
impl Pokemon for Pikachu
の部分で、Pokemon
トレイトをPikachu
型に実装しています。このPikachu
型の実装を使うには、次のように書けばいいです。
let mut pikachu = Pikachu;
pikachu.attack(Thunder);
同様にRaichu
も定義してしまいます。
struct Raichu;
impl Pokemon for Raichu {
type PokemonType = Thunder;
fn attack(&mut self, _move_: Thunder) {
println!("Raichu attacks ⚡️")
}
}
Pikachu
とRaichu
はそれぞれ別の型のデータですが、同じ型のデータとして取り扱うことはできるのでしょうか?
&mut Pokemon<PokemonType=Thunder>
Swiftではclass AnyPokemon<PokemonType>: Pokemon
を定義することで問題を解決していましたが、Rustではその必要がありません。関連型がある場合にもtrait objectが使えます。ただし、関連型がある場合のtrait objectの型は&mut Pokemon<PokemonType=Thunder>
のような記法で、関連型を具体的に指定します。
let electric_pokemon: Vec<&mut Pokemon<PokemonType=Thunder>>
= vec![&mut pikachu, &mut raichu];
for mut pokemon in electric_pokemon {
pokemon.attack(Thunder);
}