Rustは少し前に Rust by Example を一部読んだような気がしますが、だいたい忘れてしまいました。今はなんとなく雰囲気でRustを書いていますが、私のメンタルモデルとうまく合致しているのか、意外とスムーズに書けています。今回はRustほぼ初見プレイの感想をふわっと書いていきたいと思います。
Rustのモジュール定義の謎
Rustで少し躓いたところといえば、Rustのモジュールでは親のクレートで子のクレートをすべてmod
ナントカしないと、use
でインポートするどころかコンパイル対象にすらならないというところでした。なんかそれ面倒くさいんですが、なんとかなりませんかね? 逆にいえば、それ以外の箇所でRustのモジュールで悩むところは今のところありません。G◯言語のモジュールはかなり奇抜でなんじゃこりゃと思ったのですが、Rustはかなり素直だと思いました。
vscodeのRust拡張は置き換わったらしい
それと、かなり前にHelloWorldだけやったときにvscodeで Rust
という拡張をインストールしていてそれをそのまま使っていたのですが、これいつの間にか非推奨になっていたんですね。シンボルの自動インポートなどがうまく動かなくていちいち手動でuse
なんとかを書いていて面倒だったのですが、rust-analyzer
という拡張の方に移行したらいろいろうまく動くようになってきました。
予約語の略しかた
fn
とかpub
とかmut
とかimpl
とかの微妙な略し方の予約語はちょっと違和感ありますが、まあそれはそれで書くのが楽ですし変数名とも衝突しないので合理的だとは思います。慣れですね。
ライフタイムとの初遭遇
それで、Rustの最難関と聞いていたライフタイムや借用ですが、私の書き方が良いのか悪いのか、今のところそこで詰まることはあまりなかったです。あるとすれば、画像のデータとサイズをひとつにまとめた構造体Image
を定義しようとしたときです。データ部分が [u8]
というスライスなのですが、これじゃあコンパイル時にサイズがわからないよとコンパイラに怒られました。
#[derive(Clone, Copy)]
pub struct Image {
pub width: u32,
pub height: u32,
pub data: [u8],
}
--> src\image\player.rs:11:26
|
11 | pub static PLAYER_IMAGE: Image = Image {
| ^^^^^ doesn't have a size known at compile-time
|
= help: within `Image`, the trait `Sized` is not implemented for `[u8]`
note: required because it appears within the type `Image`
--> src\image.rs:10:12
|
10 | pub struct Image {
| ^^^^^
たしかに、[u8; 100]
みたいな固定長の配列ならコンパイル時にわかるでしょうが、スライスなのでコンパイル時にサイズはわかりません。画像のサイズはさまざまなので、固定長にするわけにはいかないわけです。
ちょっとググったところ、スライスにライフタイムを明示してやればいいらしいということがわかりました。ライフライムは、頭にシングルクォーテーションをつけた型パラメータみたいな感じらしいので(雑な理解)、なんかよくわかりませんが 'a
をつけてみます(雑な対処)。
#[derive(Clone, Copy)]
pub struct Image<'a> {
pub width: u32,
pub height: u32,
pub data: &'a [u8],
}
そうすると構造体自体もライフタイムを持つわけで、Image<'a>
になり、この構造体を扱う関数も <'a>
がつくようになり、'a
パラメータが感染していってパンデミックを起こしてしまい、最終的にlib.rs
のGame
のグローバルな定義にまで到達してしまいました。そこまできたら親切なコンパイラが「'static
を使うといいかもね!」みたいなことを言ってくれたので、'static
をつけたらうまくコンパイルが通りました。
pub struct Game<'a> {
...
}
impl <'a> Game<'a> {
...
}
lazy_static! {
static ref GAME: Mutex<Game<'static>> = Mutex::new(Game<'static>::new());
}
うーん、でもさすがにここまで'a
が感染拡大してしまうのはあまりに変だということで考え直して、Imageのほうに'static
をつけてみました。これでもOKなようです。
#[derive(Clone, Copy)]
pub struct Image {
pub width: u32,
pub height: u32,
pub data: &'static [u8],
}
これだと、ライフタイムが 'static
な配列とかだけがImage
にできるという定義になっている気がしますが、動的に画像を生成みたいなことはやらない気がしているのでこれでOKな気がします。ダメだったらまた考えます。
Rust全体の感想
Rustは、C/C++の良いところをすべて残しつつ悪いところを削ぎ落として、そこにHaskellの良いところを引っ張ってきてうまく混ぜ込んで、さらにライフタイムや借用の概念でうまいこと安全性を増した、という感じで、極めて合理的でよく練られたプログラミング言語だと思いました。しかもオーバーヘッドがほぼゼロで、効率が重要なソフトウェアには最適な言語のひとつですし、WASMのような特殊な環境にも向いていて、個人的にはもう全部Rustでいいんじゃないかな、と思いはじめるレベルです。私が設計思想の面で一番好きな言語はPureScriptですが、PureScriptはマイナーな言語なのでそれゆえの辛さはあり、実用面で現実的に考えたらRustを選ぶ場面が今後は多くなってきそうです。
気になるところがあるとしたら、やっぱり言語仕様の難しさでしょうか。私はたぶんまだライフタイムや借用の本質的な部分まではわかっていませんが、ガーベージコレクションのある言語に比べればコーディングの難易度は上がるような気はしています。特にチーム開発でメンバーの技術レベルに偏りがある場合は、Rustは選びにくいかなあと。
次回予告
次回はタイトル画面とリザルト画面(エンディング画面?)を作っていきます。これがあるといっそうゲームっぽくなります。