Posted at

#tryswiftconf で出てきたポケモンの例題をRustでやってみる

More than 3 years have passed since last update.

型消去の話で出てきたポケモンの例題を理解する #tryswiftconfで出てきた例をRustに移植してみます。


trait Pokemon

Swiftのprotocolに相当するものとして、Rustにはtraitがあります。

trait Pokemon {

type PokemonType;
fn attack(&mut self, move_: Self::PokemonType);
}

関連型 (Associated Types) が使える点を含めて、Swiftの例とほとんど一緒ですね! ですが、以下の相違点があります。



  • protocoltrait


  • typealiastype


  • funcfn


  • 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 ⚡️")
}
}

PikachuRaichuはそれぞれ別の型のデータですが、同じ型のデータとして取り扱うことはできるのでしょうか?


&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);
}