Rust について
Rust は、Stack Overflow で7年連続「最も愛されている言語ランキング」1位を獲得している
、人気のある言語です。型安全でありながら、C言語並みのパフォーマンスを発揮することができるため、特に低レベルでの処理を必要とするゲームやWebアプリケーションでの利用が増えているようです。
今回は、以前から興味があった Rust で WebAssembly モジュールを作成し、HTML上にアニメーションを描画してみました。
Rust は「最高難易度」と謳われることもあり、Python などと比べると異なる点が多いです。
クラスの概念がないことは、オブジェクト指向の言語を主に扱ってきた自分にとってかなり新鮮でした。
所有権や借用、ライフタイムなどの概念があるため、メモリ管理が非常に厳格で、このあたりは特に刺激となりました。
IDEは、JetBrainsの RustRover を使っています。
WebAssembly テンプレートの準備
Rust で WebAssembly を利用するためには、まず WebAssembly のプロジェクトテンプレートを準備します。今回は rust-webpack を使ってプロジェクトをセットアップしました。
プロジェクト用のディレクトリを作成し、以下のコマンドを実行して、Rust と WebAssembly のプロジェクトテンプレートを作成します。
npm init rust-webpack
npm install
アニメーションの描画
当初は 3Dオブジェクトのレンダリングを目指していましたが、JavaScript と Rust の連携が思った以上に大変だったので、一旦 2D でアニメーションを表示することにしました。
Rust でJS/TSと同様なレンダリングの記述をする必要があり、3D を扱うなら WebGL(Three.jsなど)で描画する方がはるかに楽かもしれません..
とはいえ、Rust は処理パフォーマンスが非常に高いため、リッチなコンテンツを作る際には大きな利点となるはずです。ゆくゆくは OpenGL などの技術と組み合わせて、より複雑なグラフィック処理にも挑戦してみたいと思っています。
今回の目的はあくまで Rust を触ってみる、ということにしておきます。
ひとまず完成した2Dアニメーション
大変だったこと
今回は、1枚の画像を範囲指定して表示するスプライト方式を使っています。
昔のゲームの手法ですね。
使用したスプライトは Game Art 2D からダウンロードできるフリー素材です。
やること自体は JavaScript と同じように、画像を読み込み、フレームごとに画像を切り替えてアニメーションを行うのですが、それに借用や変数のライフサイクルの制約がついた感じになります
クレートの使い方が独特
Cargo.toml に以下の感じで利用する関数を features に個別で追記しないといけない
そのうえで、モジュール内でも use を使ってインポートが必要
web-sys の場合
[dependencies.web-sys]
version = "0.3.22"
features = ["console", "Window", "Document", "Element", "HtmlCanvasElement", "HtmlElement",
"CanvasRenderingContext2d", "Response", "HtmlImageElement"]
メモリ管理
スコープ抜けたらアクセスできなかったり、forget() しないと動かなかったり。
Web上に画像を表示して動かすだけでもかなり大変。
JSを扱わない処理であればもう少しシンプルな気がする
unwrap()
返り値が None になる可能性がある場合必要
忘れるとコンパイルできない
まとめ
Rust でWebAssembly のモジュールを作ってみましたが、思ったより難しかった。
というか Rust でJSを操作するのがちょっとしっくりこないというか...
ただ学びは多かったです。Rust は複雑だけど楽しい。もっと純粋に Rust を深く知りたいと思いました。
次の機会には、3Dレンダリングできるモジュールか、高パフォーマンスが必要とされる処理をこなすモジュールを作りたいです。
Rust 覚書
トレイト
メソッドを定義するもので、プロパティやデータを持たない。
クロージャー
無名関数のこと。
Box
ヒープ領域に保存される、サイズが不明な変数。
Vector
長さが不明な配列。
let mut v1 = vec![1, 2, 3];
のように使用する。
struct, trait, impl
struct はデータ構造を、trait は振る舞いを定義し、impl でデータ構造に振る舞いを定義する。これらを組み合わせてクラスのように扱える。
シャドーイング
同じ変数名で再定義することで、元の変数を隠す。ローカルスコープ内でのみ上書きしたような状態になる
参照(ref)
ポインタを取得する。
*pointer = 5
のようにポインタから値をバインドできる。
* は参照外しで使用する。
& との違い
match 文でパターンマッチする時に使い方が異なる。
Option型
Some, None を持ち、unwrap() とセットで使用する。
デバッグ用のプリント
:p
ポインタ
:?
プリミティブでない値の参照
:#?
整形された表示ができる。
len()
でバイト数を取得。
構造体を表示する場合
#[derive(Debug, Clone)]
struct Point {
x: i32,
y: i32,
}
文字列
String::from("hello")
で可変な文字列としてヒープに格納される。
不可変な文字列は static 領域に、可変にするには mut を付けてヒープに格納される。
スタックとヒープ
プリミティブ型でない場合、スタックには値ではなくポインタが保持される。8byte のポインタ、8byte の長さ(文字列なら文字数、配列ならサイズ)、8byte の容量情報が含まれる。
所有権が移動しない型
整数型(i32, u32など)、浮動小数点型(f32, f64)、ブーリアン型(bool)、文字型(char)、参照型(&T)はスタックメモリを使用し、シャローコピーで所有権が移動しない。
所有権が移動する型
String や Vector、Box などはヒープに実データを保存し、所有権システムが適用される。
return の省略
Rust では return を明示的に書かずに、セミコロンを付けないことで返り値とする。
可変参照
let s2 = &mut s1
のように可変な参照を作ると、そのライフタイムが終了するまで元の変数にはアクセスできない。
不変な参照は複数作成可能。
ダングリングポインタ
スコープ外の参照は作らないようにする必要がある。
引用符(” と ’)
シングルクォート(’)は1文字用で、4byte メモリを使用する。
Generic Lifetime Annotation
'a
はトレイトの引数のライフタイムを指定し、短い方に合わせてダングリングポインタの発生を防ぐ。
Self と self
Self は struct の型自体を指し、コンストラクタなどで使用される。self はインスタンスを指す。