tl;dr
- Rustの練習のためゲームを作った
- Boxを用いればtraitでもstructのフィールドの型にできる(トレイトオブジェクト)
- RustはDSTという仕組みを用いて継承の様な仕組みを取り入れている
はじめに
8月末からRustを始めました。勉強のために何か作ろうと思いRust-SDL2を使ったパックマン風(※1)のゲーム「rust_pack_man」を作成しました。以下みたいなやつです。ゲームのフィールド作成に苦戦したので、そこの実装について書きます。
フィールドの作成
フィールドの情報を定義したtextファイルを読み込むことでrust_pack_manはゲームのフィールドを作成します。上のgifの様なステージであれば以下の様なtextファイルになります。1セルごとに情報が設定できる様になっていて、このセルはアイテムだ。このセルは敵キャラだという形になっているシンプルなやつです。
苦戦した内容
どのセルでも同じメソッドを使ってフィールドを描画する様に実装するのに苦戦しました。オブジェクト指向なら親クラスBaseCellとか作って共通の処理を書き、ItemCellとかWallCellとかの子クラスに継承させるところでしょうか。オブジェクト指向の様な書き方については既に@nacika_insさんが12/6に書いております。
なのでtraitオブジェクトについて私は書こうと思います。Boxを用いることでtraitオブジェクトというものに変換できます(Box化)。こうするとstructのフィールドの型としてtraitが使用できる様になります。traitオブジェクトに変換するためにはオブジェクト安全(Object Safety)である必要がありますが、traitがオブジェクト安全であるためにはインスタンスメソッドのみ用いる必要がある様です(※2)。最終的なコードとしては以下に近い形になりました。
struct Field {
field_rows: Vec<FieldRow>
}
struct FieldRow {
field_cells: Vec<Box<BaseCell>>
}
trait BaseCell {
fn draw(&self) {
/* 共通処理 */
self.unique_draw();
}
fn unique_draw(&self) {}
}
struct ItemCell {}
impl BaseCell for ItemCell {
fn unique_draw(&self) {/* 略 */}
}
struct WallCell {}
impl BaseCell for WallCell {
fn unique_draw(&self) {/* 略 */}
}
何故traitはサイズ不定なの?
XXX 'static` does not have a constant size known at compile-time
XXXtraitをtraitオブジェクトに変換しないでstructのfieldの型に設定すると出てくるエラーメッセージです。traitがサイズ不定のために出てきている様です。じゃあそもそも何故サイズ不定なものなんて実装しているのでしょうか。気になったので調べたところDynamically Sized Type(DST)という単語が登場してきました。
DSTとは文字通りサイズ不定のタイプの事で主にsliceとtraitのことを指すそうです。DSTが実装された理由の1つに継承に近い仕組みを実現するためというものがあるそうです。確かに抽象クラスやinterfaceの様なことをしたい場合、動的にサイズを変更する必要がある様な気がします(※3)。しかしそれでは型検査や性能の問題が発生するためBox化やimplを用いてstructに実装することでfatポインタというものを作り、コンパイルに必要な情報を補完する様です。
最後に
JavaScriptとかしか普段書いていない人間からするとRustはかなり難しいとは思いました。しかし想像以上に日本語の情報が多く、調べれば大体何か日本語で解決策が見つかります。先人の皆様には感謝の言葉しかないです。現在rust_pack_manはWebAssembly化の真っ最中です。それについてはこちらをどうぞ。
備考
※1 筆者は実際にオリジナルのパックマンをやったことがありません。多分こんなだろうという雰囲気で作られています
※2 具体的な理由を探しましたが発見できませんでした…そもそも回避する方法もあるかもしれません
※3 筆者は言語を作成したことがないので想像の範囲でしかありません
参考リンク
気合を入れて調べたけど最後の日本語記事で大体事足りました…
- https://doc.rust-lang.org/nomicon/exotic-sizes.html#dynamically-sized-types-dsts
- https://stackoverflow.com/questions/28044231/what-does-sized-is-not-implemented-mean
- http://huonw.github.io/blog/2015/01/the-sized-trait/
- http://smallcultfollowing.com/babysteps/blog/2014/01/05/dst-take-5/
- https://ja.stackoverflow.com/questions/34317/rust-vec-にトレイトを実装したオブジェクトを格納したい