型消去の話で出てきたポケモンの例題を理解する #tryswiftconfで出てきた例をRustに移植してみます。
-
- Gistでコードを見る
- Rust Playgroundで実行する
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: 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);
}