Rustの学習
Rustの学習を始めた。
以前にいわゆるThe Book: The Rust Programming Language 日本語版を読んでいるが、読んだだけで手を動かしていなかったので、今回は試しながら進めることにする。
手元のDebian bullseye (11.5) for amd64にRustの環境を構築した。
The bookの1.1. インストールなどだと、Linux環境ではRustupによる下記のようなインストールが推奨されているようだが:
curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf | sh
この手のツールの管理はなるべくDebianに任せたかったので、aptコマンドでcargo
をインストールした(下記)。
sudo apt install cargo
cargoのバージョンは1.46.0(cargo --version
にて確認)。
The bookの1.2. Hello, World!。
まず、cargoコマンドを使わずrustcコマンドだけでHello Worldをやってみる。
$ mkdir hello_world_without_cargo
$ cd hello_world_without_cargo
下記の中身のmain.rs
を作成。
fn main() {
println!("Hello, world!");
}
rustcコマンドで実行バイナリを作成し、実行。意図通り動作した。
$ rustc main.rs
$ ./main
Hello, world!
The bookの1.3. Hello, Cargo!。
cargoコマンドを使ってHello Worldをやってみる。
$ cargo new hello_cargo
Created binary (application) `hello_cargo` package
$ cd hello_cargo
$ ls
Cargo.toml src/
$ ls src
main.rs
src/main.rs
の中身は、最初から下記のようにHello Worldプログラムになっている。
fn main() {
println!("Hello, world!");
}
cargoコマンドを使って実行。意図通り動作した。
$ cargo run
Compiling hello_cargo v0.1.0 (.../hello_cargo)
Finished dev [unoptimized + debuginfo] target(s) in 0.55s
Running `target/debug/hello_cargo`
Hello, world!
cargoが足回りで何をしているのかがいまいち見えにくいが、パッケージの依存関係の管理などは当面気にしないで済むほうが学習効率がよいので、cargoを使うことにする。
先頭から順になぞって段階的にプログラムを書いて、最終的にリスト2-6のプログラムに至る。
下記を一通り浅く体験してみるといった感じ。
-
use
宣言によるライブラリ読み込み - 不変変数と可変変数
-
String
型による文字列 -
std::io
のio::stdin
による1行入力 -
Result
型によるエラー処理 (簡単なもの) -
println!
マクロの{}
プレースホルダーによる値の表示 - 疑似乱数生成
-
match
式による場合分け - 変数のシャドーイング: 同名の新しい変数で古い変数を隠し、変数名を再利用すること
- 文字列に対する
trim
(空白などの削除) - 文字列に対する
parse
(数値への変換) -
loop
によるくり返し文、break
による脱出、continue
による次のくり返しの開始
The bookの3. 一般的なプログラミングの概念。
下記が一通り堅く説明されている。コード片は出てくるが、まとまったプログラムを書く箇所はない。
- 変数と可変性
- データ型
- スカラー型: 整数型、浮動小数点数型、論理値型、文字型
- 複合型: タプル型、配列型
- 関数
- 引数
- 本体
- 戻り値
- コメント
- 制御フロー
-
if
式 - ループ
-
loop
: 無条件ループ -
while
: 条件付きループ -
for
: コレクションの要素に対するループ
-
- ループの制御
-
break
: ループからの脱出 -
continue
: 次のループに移る
-
-
isize
とusize
については正確な説明がなかったので、下記を参照した。
-
isize - Rust
- The pointer-sized signed integer type.
The size of this primitive is how many bytes it takes to reference any location in memory. For example, on a 32 bit target, this is 4 bytes and on a 64 bit target, this is 8 bytes.
- The pointer-sized signed integer type.
-
usize - Rust
- The pointer-sized unsigned integer type.
The size of this primitive is how many bytes it takes to reference any location in memory. For example, on a 32 bit target, this is 4 bytes and on a 64 bit target, this is 8 bytes.
- The pointer-sized unsigned integer type.
配列を含むコレクションに対するサイズはusize
、インデックスはisize
と思えばよさそう。
2022-10-11訂正: 配列を含むコレクションに対するサイズとインデックスはusize
、インデックスの差分はisize
と思えばよさそう。
The bookの3章までの学習内容で、フィボナッチ数列を表示するプログラムを書いて動作させてみる。
$ cargo new fibonacci
$ cd fibonacci
src/main.rs
に下記を書く。
fn fibonacci(i: u32) -> u32 {
let a0 = 0;
let a1 = 1;
match i {
0 => a0,
1 => a1,
_ => fibonacci(i - 2) + fibonacci(i - 1),
}
}
fn main() {
for i in 0..10 {
println!("fib[{}]: {}", i, fibonacci(i));
}
}
動作させた。問題なし。
$ cargo run
Compiling fibonacci v0.1.0 (.../fibonacci)
Finished dev [unoptimized + debuginfo] target(s) in 1.10s
Running `target/debug/fibonacci`
fib[0]: 0
fib[1]: 1
fib[2]: 1
fib[3]: 2
fib[4]: 3
fib[5]: 5
fib[6]: 8
fib[7]: 13
fib[8]: 21
fib[9]: 34
使った主なものは下記。
- 関数
-
match
式による場合分け - 再帰呼び出し
-
for
ループ
The bookの4章から先は、チュートリアルの域を抜けてプログラミング言語のよくあるボトムアップな解説になり、読み物という印象が強い。もう少しコードを書きながら進められる教材を使いたい。
下記を参考にして:
RustツアーとRust by Exampleを軽く眺めて、Rustツアーのほうをやってみることにした。
Rustツアーを先頭から順に読んでいる。気になった箇所だけメモを残しておく。
-
第1章 - 基礎
- 関数の戻り値を使わない場合は、
()
(unit) を返す。戻り値の存在自体をなくすことはできない様子
- 関数の戻り値を使わない場合は、
-
第2章 - 基本制御フロー
-
..
演算子は、開始番号から終了番号の手前までの数値を生成するイテレータを作成する。例えば0..5
は、0, 1, 2, 3, 4
を生成する -
..=
演算子は、開始番号から終了番号までの数値を生成するイテレータを作成する。例えば0..=5
は、0, 1, 2, 3, 4, 5
を生成する - ループから抜ける
break
は値を返すことができる -
if
,match
, ブロックは値を返すことができる
-
-
第3章 - 基本的なデータ構造体
-
enum
にはデータを持たせることができる
-
-
第4章 - ジェネリック型
-
::<T>
演算子の名前はturbofish - Rustにはnullがない
-
Option<T>
は、Some(T)
とNone
を表現できる -
Result<T, E>
は、Ok(T)
とErr(E)
を表現できる -
main
はResult
を返せる -
?
演算子を使うと、エラー処理を簡潔に書ける -
Option
とResult
のunwrap
は、panic!
で止めてよいコードを書くには便利
-
-
第5章 - データの所有権と借用
- スコープの終わりでのデストラクトと解放のことをドロップと呼ぶ
- 構造体のドロップは、構造体が先で要素が後 (意外だった)
- 変数を関数の実引数に渡すと、所有権が関数の仮引数に移動する
- 戻り値で所有権を渡せる
-
&
演算子でリソースへのアクセスを借用できる -
&mut
演算子でリソースへの変更可能なアクセスを借用できる -
*
演算子で参照を外せる (dereference) - ライフタイムは理解しきれなかった。スキップ
-
第6章 - テキスト
-
r#"
"#
で生文字列リテラルを書ける - 文字列リテラルに対する
len
は文字数ではなくバイト数を返す -
String
はヒープ上に配置されたutf-8バイト列 -
concat
とjoin
で文字列を連結できる。concat
は区切りなし、join
は区切りあり -
format!
と{}
で、println!
のようなことを文字列で行える - 多くの型は
to_string
で文字列へと変換できる。parse
はその逆
-
Rustツアーの第7章 - オブジェクト指向プログラミングを読み進めている。題名はオブジェクト指向プログラミングだが、主題はトレイト。
Influences - The Rust Programming LanguageとWhy Rust? - #Influences | Learning Rustによれば、RustのトレイトはHaskellの型クラスを持ち込んだものと思えばよさそうだ。C++のtype_traitsとは関係がない。
(参考: Rustに影響を与えた言語たち)
ほかの言語などから来ているであろうものについて、もう1つメモしておく。
- Option
- HaskellのMaybeに似ている(Haskell 98の時点ですでに標準に入っている)。ただし、RustのOptionにはHaskellのMaybeのような失敗するかもしれない計算の連鎖をする機能はない (あるなら別のトレイトだと思うが調べていない)
- あまり知らないのだが、Standard MLのOptionは同じものを表しているように思える。参照先が示すとおりなら、1997年時点ですでにあった様子
- C++のstd::optionalとほぼ同一。これはC++17で採用されたもので、元はBoostのoptional。Boostへの追加は、どうやら2003年のようだ (参考: Boost version 1.30.0 Release Notes)
- Result
- HaskellのEitherに似ている(これもHaskell 98の時点ですでに標準に入っている)。ただし、Rustでの命名は明らかにエラー処理専用で、それ以外の用途に使うべきではないように感じる。...と思ったら、Rust ではなぜ、Either 型ではなく Result 型を採用しているのか にRustがResultに落ち着いた経緯が少し書いてあった。なるほど
-
第7章 - オブジェクト指向プログラミング
- RustはC++でいうところの実装継承機能を意図的に持っていない
-
struct A { ... }
があったとき、impl A { ... }
にて構造体A
に対するメソッドを定義できる - フィールドとメソッドは、デフォルトでは非公開。モジュール外に公開するときは
pub
を付ける- 可視性制御の境界は、構造体内部かどうかではなくモジュールなので注意。モジュールは、ツアーではまだ出てきていない
-
trait MyTrait { ... }
でトレイトを定義する。特定の構造体に対するトレイトのメソッドは、impl MyTrait for MyStruct { ... }
で定義する- この場合、
MyTrait
が抽象型、MyStruct
がデータ表現、impl MyTrait for MyStruct
が具体型と思えばよさそう
- この場合、
- トレイトは継承できる
- 構造体のメソッド呼び出しには静的ディスパッチを、トレイトのメソッド呼び出しには動的ディスパッチを使用する
- サンプルコードの
dyn
指定を外してみたら、動作はしたが、暗黙のdyn付与はdeprecatedという警告が出た。Rust 2018では許容されるが2021ではエラーになるとのこと
- サンプルコードの
- 動的ディスパッチには、C++のvtableと同様のものが使用される
- パラメータ化された型
T
を使用する際に、T
が満たすトレイトを指定できる- C++20のコンセプトと同様の機構
- データをスタックではなくヒープに置くには、スマートポインタの1つである
Box
を使う
8章からは和訳がないので英語版を使う。
-
Chapter 8 - Smart Pointers
-
*const T
と*mut T
はT型への生ポインタ。C言語のポインタとほぼ同じもの。参照外しはunsafeコードでのみ使用できる - Rustの参照は、実行時の振る舞いはポインタと等価。ただし、コンパイル時に多くの保護が課される
-
.
演算子は、C言語のものと同様のメンバ参照演算子。ただし、左辺値がポインタあるいは多段ポインタの場合は、参照外しを暗黙に行う - スマートポインタの実装などでの生ポインタの参照外しは、unsafeコード内で行う
-
std::alloc
のalloc
とLayout
は、C言語のmalloc
と同様のことを行う (詳しいことは書かれていない) -
Box
は、ヒープ上に値を保持するシンプルなスマートポインタ -
std::rc
のRc
は、参照カウント方式のスマートポインタ -
RefCell
,Mutex
,Arc
は、ツアーだけではよくわからなかった。スキップ
-
-
Chapter 9 - Project Organization and Structure
- モジュール、プログラム、ライブラリ、クレートの関係
- RustプログラムとRustライブラリは、それぞれが1つのクレート
- 1つのクレートは、モジュールの階層からなる
- 1つのクレートは、rootモジュールを持つ
- モジュールは次のものを持つことができる: グローバル変数、関数、構造体、トレイト、サブモジュール
- モジュール階層とファイルは必ずしも対応しない。モジュール階層は手動で整える必要がある
- Rustプログラムのrootモジュールは
main.rs
ファイルに格納されている - Rustライブラリのrootモジュールは
lib.rs
ファイルに格納されている - 他のモジュールやクレートを参照するには、
use
を使う (例:use std::f64::consts::PI
) -
std
はRustの標準ライブラリのクレート - モジュール
foo
を作るには、foo.rs
またはfoo/mod.rs
のどちらかに書く - モジュール外に何かを公開するには、
pub
を使う - 通常のRustプログラムでは、
std::prelude
が暗黙に読み込まれる (Box
などはここで定義されている)-
prelude
という名前はHaskellの標準モジュールPrelude
から来ている様子 (https://crates.io/keywords/prelude に "An augmented standard library in the vein of Haskell's prelude." と書かれている)
-
- ライブラリを定義する際には、通常インポートしてほしいものをそのライブラリの
prelude
サブモジュールにまとめておくことが推奨される。ただ、このprelude
はただのサブモジュールであり、std::prelude
のような特別扱いをされるわけではない
- モジュール、プログラム、ライブラリ、クレートの関係
これでRustツアーは完了。結局読むばかりになってしまった。
The bookと眺め終わったRustツアーのそれぞれの目次を見比べて、追加で学習したい箇所の候補をリストアップ。
- Rustツアーがカバーしている
- 変数、基本的な型、関数
- 制御フロー
- 配列、タプル
- 構造体、列挙型
- Generics
-
Option
とResult
- 所有権、借用、参照
- 文字列
- トレイト
Box
- 生ポインタ
- モジュールとクレート
- Rustツアーだけでは理解が不十分
- エラー処理
- ライフタイム
- スマートポインタ
- Rustツアーにない
- パッケージ
- コレクション
- 自動テスト
- 入出力
- イテレータとクロージャ
- Cargo
- crates.io
- 並行性
- マクロ
下記を参考に:
改めて参考文献を洗い出した。
- 読み物
- リファレンス
- それ以外
-
Rust by Example
- 順に読んでいくものでもリファレンスでもないが、「こういうときはどう書けばいいのか」を調べるときに便利な気がする
-
Rust by Example
組み込み関連の機能は、興味があるので把握したい。WebAssemblyは、現時点では知識がなく使うかどうかわからないので、概要を拾っておきたい。
ということで、下記の順序で読み物を読んでいこうと思う。
- The Bookを読み返して基礎を固める
- 組み込み関連のものを通しで読んで、機能を把握する
- WebAssembly関連のものをざっと眺める
演習が足りないと感じた場合には、Command Line Applications in Rustを参考にCLIな何かを作ってみるのもいいかもしれない。
初学者の段階を抜けたら、実践的な技術の参考文献をRustの良質な学習リソースをまとめるから改めて拾うのがよさそう。
The bookの下記を読み返す。
- 7. 肥大化していくプロジェクトをパッケージ、クレート、モジュールを利用して管理する
- 8. 一般的なコレクション
- 9. エラー処理
- 11. 自動テストを書く
- 12. 入出力プロジェクト:コマンドラインプログラムを構築する
- 13. 関数型言語の機能: イテレータとクロージャ
- 14. CargoとCrates.ioについてより詳しく
- 15. スマートポインタ
- 16. 恐れるな!並行性
- 19. 高度な機能
「追加で学習したい箇所」と書いたもののうち、ライフタイムは上記に含まれていない。後で考えよう。
The bookの7. 肥大化していくプロジェクトをパッケージ、クレート、モジュールを利用して管理するを読んだ。わかりにくいと感じた箇所は、下記も参考にした。
- Tour of RustのChapter 9 - Project Organization and Structure
- Rustのモジュールシステムの基本
- [Rust] ファイル分割によるクレート分割、モジュール分割、パッケージ分割
以下、理解したつもりのことのメモ。
- モジュール: 関数、構造体、トレイトなどをひとまとまりにして、そのまとまりに名前をつけたもの
-
mod my_module { fn my_func () {} }
と書くと、my_func
関数を含むmy_module
モジュールが定義される - モジュール内にモジュールを置くことができる。これらのモジュールは全体で階層構造(モジュールツリー)をなす
-
- クレート: コンパイラにとっての翻訳単位。別々のクレートは別々にコンパイルされる
- 1つのクレートは、ライブラリまたは実行可能ファイルのいずれか一方を生成する
- ライブラリを生成するクレートをライブラリクレートと呼ぶ
- 実行可能ファイルを生成するクレートをバイナリクレートと呼ぶ
- 1つのクレートは、1つのモジュールツリーからなる
- そのクレートのモジュールツリーの最上位のモジュールを、クレートルートと呼ぶ
- 1つのクレートは、ライブラリまたは実行可能ファイルのいずれか一方を生成する
- パス: モジュールツリー内の特定の要素を指し示す手段
- 例えば
crate::my_module::my_func
はパスである。見てのとおり、モジュール名同士の区切りに::
を使う - 絶対パスと相対パスの2種類がある
- クレートルートを基準にして要素を指し示すパスを絶対パスという。例えば:
-
crate::my_module::my_func
は、そのクレートのクレートルート内のmy_module
内のmy_func
を指す -
my_crate::my_module::my_func
は、my_crate
クレートのクレートルート内のmy_module
内のmy_func
を指す
-
- モジュールツリー内の現在位置を基準にして要素を指し示すパスを相対パスという。例えば:
-
my_module::my_func
は、現在のモジュール内のmy_module
モジュール内のmy_func
を指す -
self::my_module::my_func
は、現在のモジュール内のmy_module
モジュール内のmy_func
を指す -
super
は、現在のモジュールの1階層上位のモジュールを指す
-
- クレートルートを基準にして要素を指し示すパスを絶対パスという。例えば:
- 例えば
-
pub
: モジュールツリー内の要素を公開する手段- モジュールツリー内のすべての要素は、何も指定しなければ非公開である
- 例えば
pub mod my_module { ... }
と書くと、my_module
は公開される - 下記のものには
pub
を付与できる (見つけやすいところにあったものは列挙したが、ほかにもありそう)- モジュール
- 関数
- 構造体
- 構造体のフィールド
- enum
-
use
: 指定したパスの指す対象を現在のスコープ内に持ち込む手段-
use path::to::name;
と書くと、以降の現在のスコープ内でname
と書くとpath::to::name
を指すようになる (現在のスコープにpath::to
のname
が持ち込まれる) -
use path::to::name as nickname;
と書くと、以降の現在のスコープ内でnickname
と書くとpath::to::name
を指すようになる -
use path::to::module::{A, B};
と書くと、現在のスコープにA
とB
が持ち込まれる -
use path::to::module::*;
と書くと、現在のスコープにpath::to
内のすべての公開要素が持ち込まれる (*
をglob演算子と呼ぶ)
-
- モジュールツリーを複数のファイルに分割する手順
- 以下の2通りの記述は等価
-
src/main.rs
にmod my_module { pub fn my_func () {} }
と書く -
src/main.rs
にmod my_module;
と書き、src/my_module.rs
にpub fn my_func () {}
と書く
-
- 以下の2通りの記述は等価
- パッケージ: Cargoによる管理単位
- 1つのパッケージは1つのCargo.tomlを持つ
- パッケージは1つ以上のクレートを持つ
- パッケージに含まれるライブラリクレートの個数は0個または1個
- パッケージに含まれるバイナリクレートの個数は何個でもよい
Cargoはパッケージだけでなくワークスペースも扱うが、後回しにする。
-
8. 一般的なコレクション
-
Vec<T>
: ベクタ型- 同じ型の値をメモリ上に連続に並べて格納するコレクション
- 空のベクタ生成: 例えば
let v: Vec<i32> = Vec::new();
- 初期値付きのベクタ生成: 例えば
let v = vec![1, 2, 3];
- 末尾への要素追加: 例えば
v.push(4);
- 要素の参照その1: 例えば
v[0]
。添え字が範囲外だとパニックで終了する - 要素の参照その2: 例えば
v.get(0)
。戻り値の型はOption<T>
で、添え字が範囲外だとNone
が返される - 全要素の走査その1: 例えば
for i in v { ... }
。i
はv
の各要素の値 - 全要素の走査その2: 例えば
for i in &v { ... }
。i
はv
の各要素の不変参照 - 全要素の走査その3: 例えば
for i in &mut v { ... }
。i
はv
の各要素の可変参照 - 参考文献
-
String
: 文字列型-
String
は文字列を表す型- 一方、
let s = "hello";
のs
は文字列スライスで、String
と文字列スライスは別のもの
- 一方、
-
String
はVec<u8>
のwrapper - 空の文字列生成: 例えば
let s = String::new();
- 初期値付きの文字列生成: 例えば
let s = String::from("hello");
- ある変数
v
の型がDisplay
トレイトの実装型であれば、v.to_string()
で文字列に変換できる -
format!
マクロを使うことで、println!
のように書式文字列内に変数の値を手短に埋め込み、文字列として得ることができる - 文字列の連結: 例えば
let s3 = s1 + &s2;
とすると、文字列s1
と文字列s2
を連結できる。このとき、s1
はムーブされて使用できなくなる - 参照
-
-
HashMap<K, T>
: ハッシュ関数を用いた連想配列- 空のハッシュマップ生成: 例えば
let map = HashMap::new();
- 要素追加: 例えば
map.insert("blue", 10);
- 要素の参照: 例えば
let value = map.get("blue");
。value
の型はOption<T>
- 全要素の走査1: 例えば
for (key, value) in map { ... }
。key
、value
はmap
の各要素の値 - 全要素の走査2: 例えば
for (key, value) in &map { ... }
。key
、value
はmap
の各要素の不変参照 - 参考文献
- 空のハッシュマップ生成: 例えば
- 所感: コレクションに関しては、The bookを読むよりもRust by Exampleの例を眺めるほうが理解しやすいように感じる
-
-
9. エラー処理
- 回復不能なエラーに対しては
panic!
を用いる - 回復可能なエラーに対しては
Result<T, E>
を用いる。そののち、下記のいずれかを使う:- 手書きで場合分けを記述するなら
match
- エラー時に単に
panic!
させるならunwrap
- エラー時に所定の文字列で
panic!
させるならexpect
- エラー時に呼び出し元にエラーを返すなら
?
- 手書きで場合分けを記述するなら
- 回復不能なエラーに対しては
-
11. 自動テストを書く
-
#[test]
属性をつけた関数は、テスト関数になる。テスト関数は、cargo run
では実行されず、代わりにcargo test
で実行される - 値を返さないテスト関数では、下記の方法でテストを行える
-
assert!(f)
で、f
が真であることをテストできる。失敗するとpanic!
で終了する -
assert_eq!
とassert_ne!
で、等価性テストを行える - テスト関数に
#[should_panic]
属性を付けることで、panic!
することをテストできる。#[should_panic(expected = "message")]
と指定することで、panic!
時のメッセージが期待通りかどうかの確認も行える
-
-
Result<T, E>
を返すテスト関数では、下記の方法でテストを行える- テストに成功したら
OK(T)
を返す - テストに失敗したら
Err(E)
を返す
- テストに成功したら
-
#cfg[test]
注釈をつけたモジュールは、テスト時のみビルドされる -
tests
ディレクトリ内のファイルは、テスト時のみビルドされる
-
導入としては上記ぐらいで十分と思うが、実用的なテストを書く際には下記などを参考に情報収集したほうがよさそう。
#[test]
や#cfg[test]
などが出てきて気になったので、属性(attribute)について軽く調べた。
-
Attributes - The Rust Reference
- 言語としての構文の定義、ビルトインの属性の一覧などがある。調べるときには、まずここを見ればよさそう
- 属性の構文は、InnerAttribute
#![Attr]
と OuterAttribute#[Attr]
のいずれか - 属性の種類は下記のとおり
- Built-in attributes
- Macro attributes
- Derive macro helper attributes
- Tool attributes
- 属性の構文は、InnerAttribute
- 言語としての構文の定義、ビルトインの属性の一覧などがある。調べるときには、まずここを見ればよさそう
-
アトリビュート - The Rust Example日本語版
- いくつかの例あり。現時点では、下記が記載されている
#[allow(dead_code)]
-
#[crate_type = ...]
、#[crate_name = ...]
#[cfg(...)]
- いくつかの例あり。現時点では、下記が記載されている
The bookの12. 入出力プロジェクト:コマンドラインプログラムを構築する。
minigrepという小さなgrepプログラムの実装を通して、下記を扱う。
- コードの体系化
- ベクタ
- 文字列
- エラー処理
- トレイト
- ライフタイム
- テスト
順に読みながらソースコードを更新していって、できあがったソースコードは下記のとおり。
use std::env;
use std::process;
use minigrep::Config;
use minigrep::run;
fn main() {
let args: Vec<String> = env::args().collect();
let config = Config::new(&args).unwrap_or_else(|err| {
eprintln!("Problem parsing arguments: {}", err);
process::exit(1);
});
if let Err(e) = run(&config) {
eprintln!("Application error: {}", e);
process::exit(1);
}
}
use std::fs::File;
use std::io;
use std::io::prelude::*;
pub struct Config {
pub query: String,
pub filename: String,
}
impl Config {
pub fn new(args: &Vec<String>) -> Result<Config, &'static str> {
if args.len() < 3 {
return Err("not enough arguments")
}
let query = args[1].clone();
let filename = args[2].clone();
Ok(Config { query, filename })
}
}
pub fn run(config: &Config) -> Result<(), io::Error> {
let mut f = File::open(&config.filename)?;
let mut contents = String::new();
f.read_to_string(&mut contents)?;
for line in search(&config.query, &contents) {
println!("{}", line);
}
Ok(())
}
pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
let mut results = Vec::new();
for line in contents.lines() {
if line.contains(query) {
results.push(line);
}
}
results
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn one_result() {
let query = "bar";
let contents = "\
foo bar
baz";
assert_eq!(
search(query, contents),
vec!["foo bar"]
);
}
}
上記は、The bookのソースコードを多少いじっている。主な変更点は下記のとおり。
-
Config::new
の引数の型を&[String]
から&Vec<String>
へと変更-
&[String]
を知らないので避けた。今回は&Vec<String>
で十分
-
-
run
の戻り値の型をResult<(), Box<dyn std::error::Error>
ではなくResult<(), std::io::Error>
へと変更-
std::error::Error
を知らないので避けた。今回はstd::io::Error
で十分
-
コードの体系化・エラー処理・テストは、何となく感触が得られた気がする。特に、エラー処理の仕組みはすっきりしていてよい。他の言語でよく使われる例外処理のような、処理系の実装がとても複雑なものを使わないで済むのがよい。
文字列は、様子見した程度という印象。例えば、run
の contents: String
を search(..., &contents)
とするだけで search
の contents: &str
に渡せる仕掛けは、まだ理解できていない。
ベクタ・トレイト・ライフタイムは、ちょっと出てきた程度なので、理解が進んだ感触はない。
また、あまり強調はされていなかったが、所有権の扱いが印象に残った。例えば、search
関数で使っている lines
関数は、コピーではなく参照を返している。所有権の扱いが明確な分、コピーを作成する場面が少ない。
文字列と文字列スライスの理解が浅いので復習。
「run
の contents: String
を search(..., &contents)
とするだけで search
の contents: &str
に渡せる仕掛けはまだ理解できていない」と書いたが、The bookの4.3. スライス型の中盤に、下記のように書いてあった。
- 関数の引数の型を文字列スライス型の
&str
にすると、&String
型と&str
型の両方に使える
-
13. 関数型言語の機能: イテレータとクロージャ
- クロージャ
-
let f = |arg1, arg2| { arg1 + arg2 };
などと書くと、定義したクロージャが変数f
に保持される- この例のように、クロージャ内の式が1つしかないなら、
{ ... }
は省略してもよい -
let f = |arg1: u32, arg2: u32| -> u32 { arg1 + arg2 };
などと型注釈を明示してもよい
- この例のように、クロージャ内の式が1つしかないなら、
- クロージャに関する標準のトレイトは下記の3つ
-
Fn
: 環境から値を不変で借用 -
FnMut
: 環境から値を可変で借用 -
FnOnce
: 環境から値の所有権を奪う
-
-
let equal_to_x = move |z| z == x;
などとクロージャの前にmove
と書くと、環境からx
の所有権を奪う
-
- イテレータ
-
v.iter()
は、コレクションv
に対するイテレータを返す-
iter
は不変参照を、iter_mut
は可変参照を、into_iter
は所有権を返す
-
- イテレータは
Iterator
トレイトを実装している。イテレータに対するnext
メソッドの戻り値はOption<T>
型で、有効値であればSome(val)
を、無効値であればNone
を返す -
for val in v.iter() { ... }
などとすると、変数val
にはコレクションv
の値がくり返し代入される。v
の末尾に到達すると、for
ループを抜ける -
iter.collect()
は、イテレータをコレクションへと変換する。例えばlet v2 = v1.iter().collect();
とすると、v1
の中身がv2
へとコピーされる - イテレータアダプタは、イテレータを別のイテレータに変換する
-
map
は、クロージャを使って各要素を変換する。例えば:v.iter().map(|x| x + 1).collect()
-
filter
は、論理値を返すクロージャを使って各要素を残すかどうか決める。例えば:v.iter().filter(|x| x == 0).collect()
-
-
- クロージャ
イテレータに関しては、下記が参考になった。
-
14. CargoとCrates.ioについてより詳しく
- ざっと眺めた。すぐには細部を使わない気がしたため流し読みをしただけで、メモは特になし
-
15. スマートポインタ
-
Box<T>
は、ヒープ上に値を保持するシンプルなスマートポインタ -
Deref
トレイトは、スマートポインタを通常の参照のように扱えるようにする -
Drop
トレイトは、値がスコープから外れる箇所で所定の処理を行わせられるようにする -
Rc<T>
は、参照カウント方式のスマートポインタ。複数の所有権を扱える -
RefCell<T>
は、借用規則の適用を実行時に行わせられるようにするスマートポインタ
-
-
16. 恐れるな!並行性
- スレッド
-
let handle = thread::spawn(|| { ... });
などとすると、新しいスレッドを生成できる (use std::thread;
が必要) -
handle.join().unwrap();
などとすると、スレッドの終了を待つことができる
-
- メッセージ受け渡し (Message Passing)
-
let (tx, rx) = mpsc::channel();
などとすると、送信チャンネルtx
と受信チャンネルrx
を生成できる(use std::sync::mpsc
が必要)。tx
やrx
は、別のスレッドに所有権とともに渡してよい -
tx.send(val).unwrap();
などとすると、送信チャンネルtx
に値val
を所有権とともに送ることができる -
let received = rx.recv().unwrap();
などとすると、受信チャンネルrx
から値を所有権とともに受け取ることができる
-
- 状態共有
-
Mutex<T>
は、排他制御のためのスマートポインタである。ロックを獲得しないと中身を取り出せない - 複数のスレッドに複数の所有権を持たせたい場合には、
Rc<T>
のアトミックアクセス対応版であるArc<T>
とMutex<T>
を組み合わせて使う (例:let val = Arc::new(Mutex::new(0));
)
-
- スレッド
-
19. 高度な機能 - 19.5. マクロ
- Rustのマクロには、宣言的マクロと、3種類の手続き的マクロがあるとのこと
- 宣言的マクロは、他の言語で一般に言われるところのマクロにあたる。マクロの呼び出しは、展開されたコードを手書きするのと等価な意味を持つ
- 手続き的マクロについては、読んだがいまいちよくわからず
これで、10月22日に書いた下記のリストのうち、The Bookまでは完了した。
- The Bookを読み返して基礎を固める
- 組み込み関連のものを通しで読んで、機能を把握する
- WebAssembly関連のものをざっと眺める
組み込み関連とWebAssembly関連のものとして、次は下記を読む。
また、下記の概要を知っておきたい。
- ドキュメンテーション
- Rustのバージョン間の差異
これらが済んだら、初学者レベルの学習は完了でよいだろう。
先にバージョン(というよりもエディション)とドキュメンテーションについて調べた。
- エディション
- 参考資料: エディションガイド (原文: The Rust Edition Guide)
- Rustには、ツールのバージョンとは別に、エディションというものがある。例えば、Rust 2015, Rust 2018, Rust 2021 などはエディションである。エディションが異なると、文法に差異があり、新しいものは後方互換性がないこともある
- エディションはクレートごとに選択できる(Cargoの設定などにて)。エディションの異なるクレートを混在させてビルドできる
- rustcなどのRustツールは、すべてのエディションをサポートし続ける。例えばrustcを更新したとしても、Rust 2015は引き続きサポートされる
- 各エディションの概要は下記のとおり
- ドキュメンテーション
- 参考資料: The bookの14.2、Rust By Exampleの24.1
- ソースコード内に
///
または//!
を使ってコメントを書くと、ドキュメントとして扱われる-
///
は、その後に続くソースコード(関数など)に対するドキュメントになる -
//!
は、そのコメントが含まれるソースコード(クレートなど)に対するドキュメントになる - これらのコメント内ではMarkdown記法がサポートされる (サポートされる文法詳細は The rustdoc bookの4章のMarkdown に記載されている)
-
-
cargo doc
を実行すると、target/doc
内にドキュメントが生成される (cargoは背後でrustdocを使う) - なお、rustdocは単独のMarkdownファイルを使ったドキュメンテーションをサポートしているが、cargoはまだサポートしていない (参照: The rustdoc bookの1章のUsing standalone Markdown files)
The Embedded Rust Bookの和訳を読んでいる。まず、1.2節と1.3節に書かれている開発環境を準備する。
手元の環境はDebian bullseye (11.4) for amd64で、通常のrustcやcargoに加えて必要なものは下記とのこと。
項目 | インストール手段 |
---|---|
クロスコンパイラなど | rustup |
cargo-binutils | cargo |
GDB | OSごとの手段 |
OpenOCD | OSごとの手段 |
QEMU for Arm | OSごとの手段 |
上記を行うには、rustupを使う必要がある。これまではrustup経由でのcargoではなくDebian管理下のcargoを使っていたが、これを期にrustupに乗り換えることにした。手順は下記。
sudo apt purge cargo
sudo apt autoremove
curl https://sh.rustup.rs -sSf | sh
source $HOME/.cargo/env
その後に、下記を実行してクロスコンパイラなどをインストールした。
また、cargo-generateも使いたいので足しておいた。
rustup target add thumbv6m-none-eabi thumbv7m-none-eabi thumbv7em-none-eabi thumbv7em-none-eabihf
cargo install cargo-binutils
rustup component add llvm-tools-preview
sudo apt install \
gdb-multiarch \
openocd \
qemu-system-arm
cargo install cargo-generate
2.1 QEMU の手順をなぞって、cortex-m-quickstart をQEMU上のCortex-M3で実行してみる。
cargo-generateにて、プロジェクトテンプレートからプロジェクトを作成する(下記)。
% cargo generate --git https://github.com/rust-embedded/cortex-m-quickstart
🤷 Project Name: cortex-m-quickstart
🔧 Destination: .../cortex-m-quickstart ...
🔧 project-name: cortex-m-quickstart ...
🔧 Generating template ...
[ 1/25] Done: .cargo/config.toml
[ 2/25] Done: .cargo
...
[25/25] Done: src
🔧 Moving generated files into: `.../cortex-m-quickstart`...
💡 Initializing a fresh Git repository
✨ Done! New project created .../cortex-m-quickstart
.cargo/config.toml
内のターゲット設定がCortex-M3であることを確認。
...
[build]
# Pick ONE of these compilation targets
# target = "thumbv6m-none-eabi" # Cortex-M0 and Cortex-M0+
target = "thumbv7m-none-eabi" # Cortex-M3
# target = "thumbv7em-none-eabi" # Cortex-M4 and Cortex-M7 (no FPU)
# target = "thumbv7em-none-eabihf" # Cortex-M4F and Cortex-M7F (with FPU)
# target = "thumbv8m.base-none-eabi" # Cortex-M23
# target = "thumbv8m.main-none-eabi" # Cortex-M33 (no FPU)
# target = "thumbv8m.main-none-eabihf" # Cortex-M33 (with FPU)
まずそのまま src/main.rs
をビルドし、生成された実行バイナリのファイルヘッダとディスアセンブル結果を様子見。
$ cargo build
Compiling typenum v1.15.0
Compiling semver-parser v0.7.0
...
Finished dev [unoptimized + debuginfo] target(s) in 11.80s
$ cargo readobj -- --file-headers
Finished dev [unoptimized + debuginfo] target(s) in 0.02s
ELF Header:
Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
Class: ELF32
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: EXEC (Executable file)
Machine: ARM
Version: 0x1
Entry point address: 0x401
Start of program headers: 52 (bytes into file)
Start of section headers: 809116 (bytes into file)
Flags: 0x5000200
Size of this header: 52 (bytes)
Size of program headers: 32 (bytes)
Number of program headers: 4
Size of section headers: 40 (bytes)
Number of section headers: 22
Section header string table index: 20
$ cargo objdump -- --disassemble | head -n 20
Finished dev [unoptimized + debuginfo] target(s) in 0.02s
cortex-m-quickstart: file format elf32-littlearm
Disassembly of section .text:
00000400 <Reset>:
400: b580 push {r7, lr}
402: 466f mov r7, sp
404: f000 f83b bl 0x47e <__pre_init> @ imm = #118
408: e7ff b 0x40a <Reset+0xa> @ imm = #-2
40a: f240 0000 movw r0, #0
40e: f2c2 0000 movt r0, #8192
412: f240 0100 movw r1, #0
416: f2c2 0100 movt r1, #8192
41a: f000 f883 bl 0x524 <r0::zero_bss::h3496505f418710f6> @ imm = #262
41e: e7ff b 0x420 <Reset+0x20> @ imm = #-2
420: f240 0000 movw r0, #0
424: f2c2 0000 movt r0, #8192
428: f240 0100 movw r1, #0
42c: f2c2 0100 movt r1, #8192
.cargo/config
を編集し、実行にQEMUを使うよう設定する。runner = "...
の行を下記のようにアンコメントすればよい。
[target.thumbv7m-none-eabi]
# uncomment this to make `cargo run` execute programs on QEMU
runner = "qemu-system-arm -cpu cortex-m3 -machine lm3s6965evb -nographic -semihosting-config enable=on,target=native -kernel"
...
example/hello.rs
を実行。問題なく実行できた。
$ cargo run --example hello
Compiling cortex-m-quickstart v0.1.0 (/home/masaki/work/individual/2022-10-10-rust-training/cortex-m-quickstart)
Finished dev [unoptimized + debuginfo] target(s) in 0.15s
Running `qemu-system-arm -cpu cortex-m3 -machine lm3s6965evb -nographic -semihosting-config enable=on,target=native -kernel target/thumbv7m-none-eabi/debug/examples/hello`
Hello, world!
デバッガ経由での実行を試す。
1つ目の端末で下記のようにQEMUを実行。実行すると、QEMUはTCPの3333番ポートでGDB接続を待つ状態になる。
qemu-system-arm -cpu cortex-m3 -machine lm3s6965evb -nographic -semihosting-config enable=on,target=native -gdb tcp::3333 -S -kernel target/thumbv7m-none-eabi/debug/examples/hello
もう1つの端末で、下記のようにデバッグシンボル付きでGDBを起動し、ターゲットに接続して、QEMU上のプログラム実行を再開させる。
$ gdb-multiarch -q target/thumbv7m-none-eabi/debug/examples/hello
Reading symbols from target/thumbv7m-none-eabi/debug/examples/hello...
(gdb) target remote :3333
Remote debugging using :3333
cortex_m_rt::Reset () at src/lib.rs:497
497 pub unsafe extern "C" fn Reset() -> ! {
(gdb) continue
Continuing.
[Inferior 1 (process 1) exited normally]
(gdb)
末尾の continue
コマンドを実行すると、1つ目の端末の実行が再開され、Hello, world!
が表示され、QEMUの実行が完了する。
最後にGDB側の端末のGDBプロンプトに quit
を入力し、GDBを完了させる。
- 1.1. no_std
-
2.3. メモリマップドレジスタ: 用意されているかもしれないクレートについての記述
- マイクロアーキテクチャクレート
- 特定のマイクロアーキテクチャのためのクレート
- 例えばArm Cortex-Mシリーズのプロセッサのためのcortex-mクレートは、Cortex-Mコア固有の機械語命令の実行、制御レジスタの操作、割り込み制御などの機能を持つ
- ペリフェラルアクセスクレート (PAC: Peripheral Access Crate)
- 特定のマイクロコントローラやSoCのためのクレート
- 例えばTI TM4C123xのためのtm4c123xクレートは、
PWM0
ペリフェラルへのアクセス機能を持つ
- HALクレート (HAL: Hardware Abstraction Layer)
- 下層の生のペリフェラル構造体を消費し、より抽象度の高いAPIを備えたオブジェクトを提供するクレート
- ボードクレート
- 特定の開発キットや開発基板向けに、HAL同様に抽象度の高いAPIを提供するクレート
- マイクロアーキテクチャクレート
-
2.4. セミホスティング
- セミホスティングは、組み込みデバイスがホスト上でI/Oを行う仕組み。デバッガ経由で実現される。例えば組み込みデバイス上でメッセージ表示を行うと、ホストのコンソールにログが出力される
-
2.5. パニック
- stdクレートを使用しない場合は、
panic
の動作はデフォルトでは定義されない。例えばpanic-abort、panic-halt、panic-semihostingなどから1つだけ選んで指定する必要がある
- stdクレートを使用しない場合は、
-
2.6. 例外
- 実行環境のプロセッサのための例外ハンドラを指定できる。例えばCortex-Mシリーズであれば、cortex_m_rtクレートのexception属性を使うことで、例外ハンドラを指定できる
-
2.7. 割り込み
- 例外ハンドラ同様に、割り込みハンドラも指定できる。cortex_m_rtクレートの場合は、interrupt属性が提供されている
-
3. ペリフェラル: メモリマップド・ペリフェラルの扱い方
- 構造体を使ってメモリマップドレジスタを正確に表現するには、その構造体に
#[repr(C)]
修飾子を付ける - C言語で言うところのvolatile修飾子を使うには、下記のいずれかを使用する
- 物理的に1つ(あるいは限られた個数)しかないペリフェラルの状態をあちこちから参照・変更することを防ぐために、インスタンスの個数を借用チェッカを使って管理するとよい (参照: 3.3. シングルトン)
- 構造体を使ってメモリマップドレジスタを正確に表現するには、その構造体に
-
4. 静的な保証、およびゼロコスト抽象化
- Rustのビルダーパターンを使うことで、例えば操作開始前に必ず初期化しなければならないことを静的に強制できる。この制約は型を使って表現されるため、コンパイル時にチェックされ、実行時コストがかからない
-
struct Enabled;
などとすると、サイズがゼロの型を定義できる。この型は、コンパイル時には他の型と同様に扱われるが、最適化で消去されるため、実行時にはコストがかからない
-
5. 移植性
- embedded_halクレートのような抽象化を挟むことで移植性を確保する話。書かれていることは概略のみ
-
6. 並行性
- クリティカルセクション、アトミックアクセス、SyncトレイトとSendトレイト、Mutexなどについて。この章も概略のみ
-
7. コレクション
- VecやStringなどのコレクションはstdクレートで提供されるため、
no_std
環境ではそのままでは使用できない。これは、これらのクレートがグローバルアロケータによるヒープの動的割り当てを使うことに由来する。no_std
環境でコレクションを使う方法として、この章に書かれているものは下記の2通り - stdクレートと同一のコレクション群を使う方法: 下記3つを行えばよい
- allocクレートを読み込む
-
global_allocator
属性を使って、グローバルアロケータを定義する -
alloc_error_handler
属性を使って、OOM(Out Of Memory)エラー処理方法を定義する
-
heaplessクレートを使う方法
- ヒープを使用しないコレクションを使うことができる
- 例えば
heapless::Vec
は、stdのVecに似ているが、最初に容量を指定する必要があるなどの差異がある
- VecやStringなどのコレクションはstdクレートで提供されるため、
-
8. 組込みC開発者へのヒント
- 組み込み用途のC言語で行っていたことをRustでどう扱うのかのヒント集。他の章のおさらいのようなもの
-
9. 相互運用性
- RustからCコードを使う方法、その逆、ビルドシステムの都合のつけ方など
これでThe Embedded Rust Bookの和訳は読了。
rustupとcargoについて、改めて調べた。すでに両方を使ってみているのでおおよそはわかるのだが、どちらにも「パッケージマネージャ」という役割がついていて境界線がいまいちしっくりきていなかったため。
- rustup
- 参考文献: The rustup bookのConcepts
- rustupは、Rust公式のパッケージマネージャである。下記のようなものを管理する
- ツールチェイン: rustcやcargoなど
- コード生成対象のターゲットプラットフォーム: native、クロスビルドなど
- rustupをインストールするには、Unix環境では
curl https://sh.rustup.rs -sSf | sh
を実行すればよい。このコマンドは、rustup-init.sh
というシェルスクリプトをダウンロードし、引数なしで実行する。これにより、rustupがデフォルトのインストール先である~/.cargo/bin
へとインストールされる
- cargo
- 参考文献: The cargo bookのWhy Cargo Exists
- cargoは、Rust公式のビルドシステム兼パッケージマネージャである。下記のようなことを行う
- ユーザが記述したパッケージのビルドや実行
- 依存するパッケージの取得やビルド
- rustupを使っている場合には、cargoのインストールはrustupが行う
両者の差異と共通点については、下記が参考になった。
-
rustup component addとcargo installの違い
- 差異: rustupはバイナリしか扱わないが、cargoはビルドを行える
- 共通点: ものによっては、rustupでもcargoでもインストールできる
Rust and WebAssemblyを読むにあたり、まずWasmとは何かを調べた。参考にしたものは下記。
下記のように理解した。
- WebAssembly(Wasm)は、スタック方式の仮想マシンの命令セットである
- Wasmは、下記を行いやすいよう設計されている
- 様々なプログラミング言語からWasmへとコンパイルしやすい
- Web上のクライアントやサーバで実行しやすい
- Wasmは高速に実行できる
- .wasmはバイナリフォーマットのため、仮想マシンで実行する前の事前準備がほぼ不要 (JavaScriptは字句解析や構文解析を要する)
- Wasmの仮想マシンは、実行効率を重視して設計されている
- Wasm 1.0は、主要な4つのブラウザ: Firefox、Chrome、Safari、Edge でサポートされている
Rust and WebAssemblyの和訳を読んでいる。
-
2. なぜRust and WebAssembly?
- Webアプリで使われるJavaScriptは、処理性能が遅いことが課題。原因は、動的型付けとガベージコレクション
- Rustは、静的型付けで、かつガベージコレクションを必要としない。また必須のランタイムコードがないため、コードサイズが小さく、Web経由でのロードが軽量で済む
- Rust + Wasmは、JavaScriptとうまく組み合わせられる。既存のJavaScript資産の一部分だけを置き換えるなどが容易
-
3. 背景とコンセプト
- Wasmとは何か: すでに調べてあるのでスキップ
- WasmはWebだけのためのものか: そういう仕様制約はないが、目下の用途はWebである
Rust and WebAssemblyの和訳の4. チュートリアルを行う。
まずセットアップの記述にしたがい、下記のものをセットアップする。
- Rustツールチェイン、cargo-generate
- すでにセットアップ済み (参照: 2022-12-04の記述)
- npm
- すでにセットアップ済み。Node.jsは19.3.0、npmは9.2.0
- wasm-pack
- 未実施。後述
wasm-packのインストールは、上記のチュートリアルでは下記を案内しているが:
-
wasm-pack
-
curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
を実行
-
下記を見ると、cargoがよろしく扱ってくれるようなので:
-
RustからWebAssemblyにコンパイルする - WebAssembly | MDNのwasm-pack
-
cargo install wasm-pack
を実行
-
書かれているとおりにした。
$ cargo install wasm-pack
Updating crates.io index
Downloaded wasm-pack v0.10.3
Downloaded 1 crate (423.0 KB) in 0.90s
Installing wasm-pack v0.10.3
Downloaded native-tls v0.2.11
Downloaded mime_guess v2.0.4
Downloaded openssl-sys v0.9.80
...
Downloaded 145 crates (12.1 MB) in 3.49s (largest was `curl-sys` at 3.0 MB)
Compiling libc v0.2.139
Compiling autocfg v1.1.0
Compiling proc-macro2 v1.0.49
Compiling quote v1.0.23
...
Compiling wasm-pack v0.10.3
Finished release [optimized] target(s) in 4m 12s
Installing /home/masaki/.cargo/bin/wasm-pack
Installed package `wasm-pack v0.10.3` (executable `wasm-pack`)
Rust and WebAssemblyの4. Tutorialの4.2. Hello, World!を行う。
なお、和訳は4.2. Hello, World!の途中までしか翻訳されていない様子だったため、以降は原文を直接読むことにした。
流れは下記のとおり。
-
wasm-pack-templateをベースに、
wasm-game-of-life
という名前のプロジェクトを作成 - ビルド
- 実行用のWebページをローカル環境に新規作成
- そのWebページにビルドしたプロジェクトをインストール
- 実行
- Webブラウザを使って動作確認
実際の操作は下記のとおり行った。
その1。
cargo generateコマンドにて、wasm-pack-templateを持ってきて、wasm-game-of-life
という名前のプロジェクトを作成。
$ cargo generate --git https://github.com/rustwasm/wasm-pack-template
🤷 Project Name: wasm-game-of-life
🔧 Destination: .../wasm-game-of-life ...
🔧 project-name: wasm-game-of-life ...
🔧 Generating template ...
[ 1/12] Done: .appveyor.yml
[ 2/12] Done: .gitignore
[ 3/12] Done: .travis.yml
[ 4/12] Done: Cargo.toml
[ 5/12] Done: LICENSE_APACHE
[ 6/12] Done: LICENSE_MIT
[ 7/12] Done: README.md
[ 8/12] Done: src/lib.rs
[ 9/12] Done: src/utils.rs
[10/12] Done: src
[11/12] Done: tests/web.rs
[12/12] Done: tests
🔧 Moving generated files into: `.../wasm-game-of-life`...
💡 Initializing a fresh Git repository
✨ Done! New project created .../wasm-game-of-life
$ cd wasm-game-of-life
その2。
wasm-packにてビルド。なお、rustupのtargetにwasm32-unknown-unknown
はインストールされていなかったが、自動で追加された。
$ wasm-pack build
[INFO]: Checking for the Wasm target...
info: downloading component 'rust-std' for 'wasm32-unknown-unknown'
info: installing component 'rust-std' for 'wasm32-unknown-unknown'
18.8 MiB / 18.8 MiB (100 %) 12.0 MiB/s in 1s ETA: 0s
[INFO]: Compiling to Wasm...
Compiling proc-macro2 v1.0.49
Compiling unicode-ident v1.0.6
Compiling quote v1.0.23
Compiling log v0.4.17
Compiling wasm-bindgen-shared v0.2.83
Compiling syn v1.0.107
Compiling cfg-if v1.0.0
Compiling bumpalo v3.11.1
Compiling once_cell v1.16.0
Compiling wasm-bindgen v0.2.83
Compiling wasm-bindgen-backend v0.2.83
Compiling wasm-bindgen-macro-support v0.2.83
Compiling wasm-bindgen-macro v0.2.83
Compiling console_error_panic_hook v0.1.7
Compiling wasm-game-of-life v0.1.0 (.../wasm-game-of-life)
warning: function `set_panic_hook` is never used
--> src/utils.rs:1:8
|
1 | pub fn set_panic_hook() {
| ^^^^^^^^^^^^^^
|
= note: `#[warn(dead_code)]` on by default
warning: `wasm-game-of-life` (lib) generated 1 warning
Finished release [optimized] target(s) in 8.50s
[INFO]: Installing wasm-bindgen...
[INFO]: Optimizing wasm binaries with `wasm-opt`...
[INFO]: Optional fields missing from Cargo.toml: 'description', 'repository', and 'license'. These are not necessary, but recommended
[INFO]: :-) Done in 27.14s
[INFO]: :-) Your wasm pkg is ready to publish at .../wasm-game-of-life/pkg.
その3。
実行用のWebページを、プロジェクトディレクトリ内に www/
として新規作成。
$ npm init wasm-app www
🦀 Rust + 🕸 Wasm = ❤
$ cd www
$ npm install
npm WARN old lockfile
npm WARN old lockfile The package-lock.json file was created with an old version of npm,
npm WARN old lockfile so supplemental metadata must be fetched from the registry.
npm WARN old lockfile
npm WARN old lockfile This is a one-time fix-up, please be patient...
npm WARN old lockfile
npm WARN deprecated ini@1.3.5: Please update to ini >=1.3.6 to avoid a prototype pollution issue
npm WARN deprecated urix@0.1.0: Please see https://github.com/lydell/urix#deprecated
npm WARN deprecated resolve-url@0.2.1: https://github.com/lydell/resolve-url#deprecated
npm WARN deprecated source-map-url@0.4.0: See https://github.com/lydell/source-map-url#deprecated
npm WARN deprecated debug@3.2.6: Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)
npm WARN deprecated debug@3.2.6: Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)
npm WARN deprecated debug@3.2.6: Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)
npm WARN deprecated debug@4.1.1: Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)
npm WARN deprecated chokidar@2.1.8: Chokidar 2 does not receive security updates since 2019. Upgrade to chokidar 3 with 15x fewer dependencies
npm WARN deprecated querystring@0.2.0: The querystring API is considered Legacy. new code should use the URLSearchParams API instead.
npm WARN deprecated uuid@3.4.0: Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.
npm WARN deprecated uuid@3.3.2: Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.
npm WARN deprecated source-map-resolve@0.5.2: See https://github.com/lydell/source-map-resolve#deprecated
npm WARN deprecated mkdirp@0.5.1: Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)
added 587 packages, and audited 588 packages in 18s
18 packages are looking for funding
run `npm fund` for details
35 vulnerabilities (1 low, 4 moderate, 26 high, 4 critical)
To address all issues, run:
npm audit fix
Run `npm audit` for details.
その4。
そのWebページの一部を変更し、ビルドしたwasm-game-of-lifeをインストールする。
www/
内の package.json
を適当なエディタで開いて、devDependencies
という記述の手前に下記のように "dependencies": { ... },
という3行を追加。
"homepage": "...",
"dependencies": {
"wasm-game-of-life": "file:../pkg"
},
"devDependencies": {
"hello-wasm-pack": "...",
www/
内の index.js
を適当なエディタで開いて、
冒頭の1行が import * as wasm from "hello-wasm-pack";
となっているのを
import * as wasm from "wasm-game-of-life";
へと変更する。
再度 npm install
を実行し、www/
内にwasm-game-of-lifeをインストールする。
$ npm install
added 1 package, and audited 589 packages in 2s
18 packages are looking for funding
run `npm fund` for details
34 vulnerabilities (1 low, 4 moderate, 25 high, 4 critical)
To address all issues, run:
npm audit fix
Run `npm audit` for details.
その5。
実行し、http://localhost:8080
でのサービスを開始。...と思ったが、エラー発生。
% npm run start
> create-wasm-app@0.1.0 start
> webpack-dev-server
(node:258260) [DEP0111] DeprecationWarning: Access to process.binding('http_parser') is deprecated.
(Use `node --trace-deprecation ...` to show where the warning was created)
ℹ 「wds」: Project is running at http://localhost:8080/
ℹ 「wds」: webpack output is served from /
ℹ 「wds」: Content not from webpack is served from .../wasm-game-of-life/www
node:internal/crypto/hash:71
this[kHandle] = new _Hash(algorithm, xofLen);
^
Error: error:0308010C:digital envelope routines::unsupported
at new Hash (node:internal/crypto/hash:71:19)
at Object.createHash (node:crypto:140:10)
at module.exports (.../wasm-game-of-life/www/node_modules/webpack/lib/util/createHash.js:135:53)
at NormalModule._initBuildHash (.../wasm-game-of-life/www/node_modules/webpack/lib/NormalModule.js:417:16)
at handleParseError (.../wasm-game-of-life/www/node_modules/webpack/lib/NormalModule.js:471:10)
at .../wasm-game-of-life/www/node_modules/webpack/lib/NormalModule.js:503:5
at .../wasm-game-of-life/www/node_modules/webpack/lib/NormalModule.js:358:12
at .../wasm-game-of-life/www/node_modules/loader-runner/lib/LoaderRunner.js:373:3
at iterateNormalLoaders (.../wasm-game-of-life/www/node_modules/loader-runner/lib/LoaderRunner.js:214:10)
at Array.<anonymous> (.../wasm-game-of-life/www/node_modules/loader-runner/lib/LoaderRunner.js:205:4)
at Storage.finished (.../wasm-game-of-life/www/node_modules/enhanced-resolve/lib/CachedInputFileSystem.js:43:16)
at .../wasm-game-of-life/www/node_modules/enhanced-resolve/lib/CachedInputFileSystem.js:79:9
at .../wasm-game-of-life/www/node_modules/graceful-fs/graceful-fs.js:78:16
at FSReqCallback.readFileAfterClose [as oncomplete] (node:internal/fs/read_file_context:68:3) {
opensslErrorStack: [ 'error:03000086:digital envelope routines::initialization error' ],
library: 'digital envelope routines',
reason: 'unsupported',
code: 'ERR_OSSL_EVP_UNSUPPORTED'
}
Node.js v19.3.0
エラーコードである ERR_OSSL_EVP_UNSUPPORTED
をキーワードに検索してみたところ、下記が見つかった。
環境変数 NODE_OPTIONS
に --openssl-legacy-provider
を設定すればよいらしい。
対策を足して再度実行。今度は下記のように成功。
$ export NODE_OPTIONS==--openssl-legacy-provider
$ npm run start
> create-wasm-app@0.1.0 start
> webpack-dev-server
(node:257519) [DEP0111] DeprecationWarning: Access to process.binding('http_parser') is deprecated.
(Use `node --trace-deprecation ...` to show where the warning was created)
ℹ 「wds」: Project is running at http://localhost:8080/
ℹ 「wds」: webpack output is served from /
ℹ 「wds」: Content not from webpack is served from .../wasm-game-of-life/www
ℹ 「wdm」: Hash: 8545b52b4ae243159519
Version: webpack 4.43.0
Time: 430ms
Built at: 2022/12/24 19:32:20
Asset Size Chunks Chunk Names
0.bootstrap.js 3.4 KiB 0 [emitted]
8e8fa9289c240ac706a1.module.wasm 872 bytes 0 [emitted] [immutable]
bootstrap.js 369 KiB main [emitted] main
index.html 297 bytes [emitted]
Entrypoint main = bootstrap.js
[0] multi (webpack)-dev-server/client?http://localhost:8080 ./bootstrap.js 40 bytes {main} [built]
[./bootstrap.js] 279 bytes {main} [built]
[./index.js] 56 bytes {0} [built]
[./node_modules/ansi-html/index.js] 4.16 KiB {main} [built]
[./node_modules/ansi-regex/index.js] 135 bytes {main} [built]
[./node_modules/hello-wasm-pack/hello_wasm_pack.js] 698 bytes {0} [built]
[./node_modules/strip-ansi/index.js] 161 bytes {main} [built]
[./node_modules/webpack-dev-server/client/index.js?http://localhost:8080] (webpack)-dev-server/client?http://localhost:8080 4.29 KiB {main} [built]
[./node_modules/webpack-dev-server/client/overlay.js] (webpack)-dev-server/client/overlay.js 3.51 KiB {main} [built]
[./node_modules/webpack-dev-server/client/socket.js] (webpack)-dev-server/client/socket.js 1.53 KiB {main} [built]
[./node_modules/webpack-dev-server/client/utils/createSocketUrl.js] (webpack)-dev-server/client/utils/createSocketUrl.js 2.91 KiB {main} [built]
[./node_modules/webpack-dev-server/client/utils/log.js] (webpack)-dev-server/client/utils/log.js 964 bytes {main} [built]
[./node_modules/webpack-dev-server/client/utils/reloadApp.js] (webpack)-dev-server/client/utils/reloadApp.js 1.59 KiB {main} [built]
[./node_modules/webpack-dev-server/client/utils/sendMessage.js] (webpack)-dev-server/client/utils/sendMessage.js 402 bytes {main} [built]
[./node_modules/webpack/hot sync ^\.\/log$] (webpack)/hot sync nonrecursive ^\.\/log$ 170 bytes {main} [built]
+ 21 hidden modules
ℹ 「wdm」: Compiled successfully.
なお、フォアグラウンド実行になるため、シェルのプロンプトは戻ってこない。
その6。ローカルホスト上のWebブラウザにて、http://localhost:8080
を開く。
Hello, wasm-game-of-life!
というメッセージが表示された。
動作確認が完了したら、npm run start
を Ctrl-C で停止させた。
Rust and WebAssemblyの4.2. Hello, World!を手元で動作させたが、内容の理解が浅かったので、登場したツールやテンプレートがどういうものなのかを調べた。
-
wasm-pack
- Rustで書かれたソースコードからWasmコードを生成・実行するためのツールのひとつ。生成されたWasmコードは、Webブラウザ・Node.js・JavaScriptを介して実行される
- 概要はHello wasm-pack!に書かれている
-
wasm-pack-template
- wasm-packのためのプロジェクトのテンプレート。npmのパッケージを生成できる。cargo generateして使うことができる
-
create-wasm-app
- wasm-packで作成したnpmパッケージを動作させるための
npm init
用のテンプレート
- wasm-packで作成したnpmパッケージを動作させるための
-
wasm-bindgen
- Rustから生成したWasmコードとJavaScriptとの間の橋渡しをするためのRustパッケージ
- 詳細はThe
wasm-bindgen
Guideに書かれている
4つあるが、いずれもThe Rust and WebAssembly Working Groupによるものだし、読んでいる文書のRust and WebAssemblyも同じグループがメンテナンスしているものだった。位置関係は下記のように理解すればだいたい正しい気がする。
- wasm-packは、wasm-bindgenを使って作られている
- wasm-pack-templateとcreate-wasm-appは、ユーザーがwasm-packを使うときに利用すると便利なテンプレート
もっとシンプルなWasm実行を試しておきたかったので、いくつか調べた。
- WebAssembly Core Specification
-
WASI
- WASI: The WebAssembly System Interface
- WASIは、WasmからOSへのアクセスを提供する一連のAPIである。例えばファイル、ファイルシステム、ソケット、時刻、乱数などを扱うことができる
-
Wasmtime
- WASIランタイム実装のひとつ。Bytecode Allianceによるもの
-
cargo-wasi
- cargo-wasi: A lightweight Cargo subcommand to build Rust code for the
wasm32-wasi
target
- cargo-wasi: A lightweight Cargo subcommand to build Rust code for the
- ほか
-
興味のおもむくままにWASM/WASIらへん
- Wasm CoreとWASIの関係などについて述べている
-
WasmtimeでWebAssemblyを動かしてみる
- Wasmtimeを使ってWasmコードを実行する手順を紹介した記事
- 冒頭にWasm、WASI、Wasmtimeが何であるかを書いていてわかりやすい
- cargo-wasi で WebAssembly コンパイルするには
-
興味のおもむくままにWASM/WASIらへん
下記のように理解した。
- Wasm Coreは、仮想マシンの命令セットのみを定義したもの。入出力機能は別途足す必要がある
- Wasm CoreにWASIとそのランタイムサポートを足せば、通常のOSの機能を介してファイル入出力などを行えるようになる
- WASIのランタイム実装のひとつがWasmtime
- Wasm Coreにwasm-bindgenを足せば、Webブラウザ・Node.js・JavaScriptを介して入出力を行えるようになる
RustでWASIを扱う場合は、cargo-wasiを使うのが手早そうに見えたので、下記の順序でやってみた。
- cargo-wasiをインストール
- wasmtimeをインストール
- Hello worldプログラムを作成
- ビルド
- WASIを使って実行
その1。cargoを使ってcargo-wasiをインストール。
$ cargo install cargo-wasi
Updating crates.io index
Downloaded cargo-wasi v0.1.26
Downloaded 1 crate (13.7 KB) in 0.54s
Installing cargo-wasi v0.1.26
Downloaded cargo-wasi-exe-x86_64-unknown-linux-musl v0.1.26
Downloaded 1 crate (4.4 MB) in 2.55s
Compiling cargo-wasi-exe-x86_64-unknown-linux-musl v0.1.26
Compiling cfg-if v1.0.0
Compiling cargo-wasi v0.1.26
Finished release [optimized] target(s) in 7.08s
Installing .../.cargo/bin/cargo-wasi
Installed package `cargo-wasi v0.1.26` (executable `cargo-wasi`)
その2。Wasmtime公式の手順を使ってwasmtimeをインストール。
$ curl https://wasmtime.dev/install.sh -sSf | bash
Installing latest version of Wasmtime (v4.0.0)
Checking for existing Wasmtime installation
Fetching archive for Linux, version v4.0.0
https://github.com/bytecodealliance/wasmtime/releases/download/v4.0.0/wasmtime-v4.0.0-x86_64-linux.tar.xz
######################################################################## 100.0%
Creating directory layout
Extracting Wasmtime binaries
wasmtime-v4.0.0-x86_64-linux/
wasmtime-v4.0.0-x86_64-linux/README.md
wasmtime-v4.0.0-x86_64-linux/wasmtime
wasmtime-v4.0.0-x86_64-linux/LICENSE
Editing user profile (.../.zshrc)
Finished installation. Open a new terminal to start using Wasmtime!
なお、上記の操作により、~/.zshrc
の末尾に下記が追記されていた。
export WASMTIME_HOME="$HOME/.wasmtime"
export PATH="$WASMTIME_HOME/bin:$PATH"
その3。cargoを使ってHello worldプログラムを作成。
$ cargo new wasi-hello-world
Created binary (application) `wasi-hello-world` package
$ cd wasi-hello-world
$ cat src/main.rs
fn main() {
println!("Hello, world!");
}
その4。cargo wasiを使ってビルド。
% cargo wasi build
info: downloading component 'rust-std' for 'wasm32-wasi'
info: installing component 'rust-std' for 'wasm32-wasi'
19.4 MiB / 19.4 MiB (100 %) 12.0 MiB/s in 1s ETA: 0s
Compiling wasi-hello-world v0.1.0 (.../wasi-hello-world)
Finished dev [unoptimized + debuginfo] target(s) in 0.55s
このcargo wasi build
によって、Rust開発環境のtargetにwasm32-wasi
が足される。これは、下記で確認できる。
$ rustup target list --installed
wasm32-unknown-unknown
wasm32-wasi
x86_64-unknown-linux-gnu
その5。作成したプログラムをWasmtimeを使って実行。意図どおり、コンソールにHello, world!
が出力された。
$ cargo wasi run
Finished dev [unoptimized + debuginfo] target(s) in 0.01s
Running `.../.cargo/bin/cargo-wasi target/wasm32-wasi/debug/wasi-hello-world.wasm`
Running `target/wasm32-wasi/debug/wasi-hello-world.wasm`
Hello, world!
おまけ。下記のwabtを使って、生成されたWasmコードを眺めてみる。
-
wabt
- WABT: The WebAssembly Binary Toolkit
-
wasm-objdump
,wat2wasm
,wasm2wat
などのWebAssembly関連のバイナリ操作ツールをまとめたもの
まず、aptコマンドを使ってwabtをインストール。
sudo apt install wabt
作成したWasmコードは target/wasm32-wasi/debug/wasi-hello-world.wasm
なので、wasm-objdump
で様子を見てみたら、下記のようになった。Wasmコード内に257の関数が入っていること、wasi_hello_world::main::...
関数はstd::io::stdio::_print::...
を呼び出していることがわかる。
$ wasm-objdump -x ./target/wasm32-wasi/debug/wasi-hello-world.wasm
wasi-hello-world.wasm: file format wasm 0x1
Section Details:
Type[17]:
- type[0] () -> nil
- type[1] () -> i32
- type[2] (i32) -> nil
...
Import[4]:
...
Function[257]:
- func[4] sig=3 <dlmalloc>
- func[5] sig=2 <dlfree>
- func[6] sig=5 <dispose_chunk>
- func[7] sig=8 <memcpy>
- func[8] sig=8 <memmove>
- func[9] sig=6 <realloc>
- func[10] sig=8 <core::fmt::Formatter::pad::hdf0d4057a6991eca>
- func[11] sig=11 <std::panicking::rust_panic_with_hook::hc5c73bd02fe928d4>
...
...
Code[257]:
- func[4] size=6712 <dlmalloc>
- func[5] size=1708 <dlfree>
- func[6] size=1632 <dispose_chunk>
- func[7] size=1296 <memcpy>
- func[8] size=1283 <memmove>
- func[9] size=1073 <realloc>
- func[10] size=970 <core::fmt::Formatter::pad::hdf0d4057a6991eca>
...
...
$ wasm-objdump -d ./target/wasm32-wasi/debug/wasi-hello-world.wasm
wasi-hello-world.wasm: file format wasm 0x1
Code Disassembly:
000315 func[4] <dlmalloc>:
000316: 0b 7f | local[0..10] type=i32
...
00be4f func[105] <wasi_hello_world::main::h4b16048bf98b094f>:
00be50: 0f 7f | local[0..14] type=i32
00be52: 23 00 | global.get 0
00be54: 21 00 | local.set 0
00be56: 41 20 | i32.const 32
00be58: 21 01 | local.set 1
00be5a: 20 00 | local.get 0
00be5c: 20 01 | local.get 1
00be5e: 6b | i32.sub
00be5f: 21 02 | local.set 2
00be61: 20 02 | local.get 2
00be63: 24 00 | global.set 0
00be65: 41 08 | i32.const 8
00be67: 21 03 | local.set 3
00be69: 20 02 | local.get 2
00be6b: 20 03 | local.get 3
00be6d: 6a | i32.add
00be6e: 21 04 | local.set 4
00be70: 20 04 | local.get 4
00be72: 21 05 | local.set 5
00be74: 41 a8 80 c0 00 | i32.const 1048616
00be79: 21 06 | local.set 6
00be7b: 41 01 | i32.const 1
00be7d: 21 07 | local.set 7
00be7f: 41 b0 80 c0 00 | i32.const 1048624
00be84: 21 08 | local.set 8
00be86: 41 00 | i32.const 0
00be88: 21 09 | local.set 9
00be8a: 20 05 | local.get 5
00be8c: 20 06 | local.get 6
00be8e: 20 07 | local.get 7
00be90: 20 08 | local.get 8
00be92: 20 09 | local.get 9
00be94: 10 36 | call 54 <core::fmt::Arguments::new_v1::h0abebdd0d4409549>
00be96: 41 08 | i32.const 8
00be98: 21 0a | local.set 10
00be9a: 20 02 | local.get 2
00be9c: 20 0a | local.get 10
00be9e: 6a | i32.add
00be9f: 21 0b | local.set 11
00bea1: 20 0b | local.get 11
00bea3: 21 0c | local.set 12
00bea5: 20 0c | local.get 12
00bea7: 10 2f | call 47 <std::io::stdio::_print::haf67ca1f4db3f5ea>
00bea9: 41 20 | i32.const 32
00beab: 21 0d | local.set 13
00bead: 20 02 | local.get 2
00beaf: 20 0d | local.get 13
00beb1: 6a | i32.add
00beb2: 21 0e | local.set 14
00beb4: 20 0e | local.get 14
00beb6: 24 00 | global.set 0
00beb8: 0f | return
00beb9: 0b | end
00bebb func[106] <calloc>:
00bebc: 01 7f | local[0] type=i32
...
Rust and WebAssemblyの4.4. Implementing Lifeをなぞって、ライフゲームを作る。
Rustのプロジェクトは、12月24日に作成したwasm-game-of-lifeをそのまま使う。
src/lib.rs
、www/index.html
および www/index.js
を下記のものへと書き換えた。
mod utils;
use wasm_bindgen::prelude::*;
use std::fmt;
// When the `wee_alloc` feature is enabled, use `wee_alloc` as the global
// allocator.
#[cfg(feature = "wee_alloc")]
#[global_allocator]
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
#[wasm_bindgen]
#[repr(u8)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Cell {
Dead = 0,
Alive = 1,
}
#[wasm_bindgen]
pub struct Universe {
width: u32,
height: u32,
cells: Vec<Cell>,
}
impl fmt::Display for Universe {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
for line in self.cells.as_slice().chunks(self.width as usize) {
for &cell in line {
let symbol = if cell == Cell::Dead { '◻' } else { '◼' };
write!(f, "{}", symbol)?;
}
write!(f, "\n")?;
}
Ok(())
}
}
#[wasm_bindgen]
impl Universe {
fn get_index(&self, row: u32, column: u32) -> usize {
(row * self.width + column) as usize
}
fn live_neighbor_count(&self, row: u32, column: u32) -> u8 {
let mut count = 0;
for delta_row in [self.height - 1, 0, 1].iter().cloned() {
for delta_col in [self.width - 1, 0, 1].iter().cloned() {
if delta_row == 0 && delta_col == 0 {
continue;
}
let neighbor_row = (row + delta_row) % self.height;
let neighbor_col = (column + delta_col) % self.width;
let idx = self.get_index(neighbor_row, neighbor_col);
count += self.cells[idx] as u8;
}
}
count
}
pub fn new() -> Universe {
let width = 64;
let height = 64;
let cells = (0..width * height)
.map(|i| {
if i % 2 == 0 || i % 7 == 0 {
Cell::Alive
} else {
Cell::Dead
}
})
.collect();
Universe {
width,
height,
cells,
}
}
pub fn tick(&mut self) {
let mut next = self.cells.clone();
for row in 0..self.height {
for col in 0..self.width {
let idx = self.get_index(row, col);
let cell = self.cells[idx];
let live_neighbors = self.live_neighbor_count(row, col);
let next_cell = match (cell, live_neighbors) {
(Cell::Alive, x) if x < 2 => Cell::Dead,
(Cell::Alive, 2) | (Cell::Alive, 3) => Cell::Alive,
(Cell::Alive, x) if x > 2 => Cell::Dead,
(Cell::Dead, 3) => Cell::Alive,
(otherwise, _) => otherwise,
};
next[idx] = next_cell;
}
}
self.cells = next;
}
pub fn render(&self) -> String {
self.to_string()
}
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Game of Life</title>
<style>
body {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
</style>
</head>
<body>
<pre id="game-of-life-canvas"></pre>
<noscript>This page contains webassembly and javascript content, please enable javascript in your browser.</noscript>
<script src="./bootstrap.js"></script>
</body>
</html>
import { Universe } from "wasm-game-of-life";
const pre = document.getElementById("game-of-life-canvas");
const universe = Universe.new();
const renderLoop = () => {
pre.textContent = universe.render();
universe.tick();
requestAnimationFrame(renderLoop);
};
requestAnimationFrame(renderLoop);
まず、プロジェクトのトップディレクトリで wasm-pack build
を実行し、Wasmを更新した。
続いて、./www
にて npm install
で wasm-game-of-life
を更新。
...と思ったが、うまく更新されなかったので、下記のように通常のcpコマンドで上書きした。
cp -f ./pkg/* ./www/node_modules/wasm-game-of-life
./www
にて npm run start
を実行し、Webブラウザで http://localhost:8080
を開くと、テキスト表示のライフゲームが動作した。
ある状態でのスクリーンショットは下記。実際には、ウェイトなどが入っていないため状態がどんどん遷移していく。
◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻
◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻
◻◻◻◻◻◻◼◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◼◻
◻◻◻◻◻◼◻◻◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◻◻◼
◻◻◻◻◻◻◼◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◻◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◼◻
◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◻◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻
◻◻◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◻◻◻◻◻◻◻◻◻◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻
◻◼◻◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻
◻◼◻◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻
◻◻◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻
◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻
◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◻◻◻◻◻◻◻◻◻◼◼◻◻◻◻◼◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻
◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◻◻◻◻◻◻◻◻◼◻◻◼◻◻◼◻◻◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻
◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◼◻◻◻◼◻◼◻◻◻◻◻◻◻◼◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻
◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◻◻◻◻◻◻◻◼◻◻◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻
◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻
◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻
◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻
◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻
◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻
◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◻◻◻◻◻◻◻
◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◼◻◼◻◻◻
◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◼◻◻◻◼◼◻◼◻◻◻
◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◻◻◼◼◻◻◻◼◻◻◻
◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◼◼◻◻◻◻◼◼◻◻◻
◻◼◼◻◻◻◻◻◻◻◻◻◼◻◻◻◻◻◻◻◻◻◻◻◼◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◼◼◼◻◻◻◻◻◻
◻◼◼◻◻◻◻◻◻◻◻◻◼◻◻◻◻◻◻◻◻◻◻◼◻◻◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◼◻◻◻◻◻◻◻◻◼◻◻◻◻◼◼◻◻◻
◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◻◻◼◻◻◻◻◻◻◻◻◼◻◻◻◻◻◻◻◻
◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◻◼◻◻◻◻◻◻◻◻◻◼◻◻◻◻◻◻◻
◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻
◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻
◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻
◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◻◻◻◻◻◻◻◻◻◻◻◻◼◻◻◻◻◻◻◻
◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◼◻◻◻◻◻◻◻◻◻◻◼◻◼◻◻◻◻◻◻
◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◻◻◼◻◻◻◻◻◻◻◻◻◼◻◼◼◻◻◻◻◻
◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◻◼◻◻◻◻◻◻◻◻◻◼◻◼◼◻◻◻◻◻
◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◼◻◻◻◻◻◻◻◻◻◻◻◼◼◼◻◻◻◻◻
◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◻◻◻◼◻◻◻
◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◻◻◻
◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◼◻◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◻◻◻
◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◻◼◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻
◻◻◻◻◻◻◻◻◻◻◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◻◻◻◼◻◻◻◻◻◻◻◻◻◻◻◻◻◼◻◻◻◻
◻◻◻◻◻◻◻◻◻◼◻◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◼◻◼◼◻◻◻◻◻◻◻◻◻◻◼◻◻◻◻◻◻
◻◻◻◻◼◻◻◻◻◻◼◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◼◼◼◻◻◻◻◻◻
◻◻◻◼◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◼◼◻◻◻◻◻◻◻
◻◻◼◻◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◻◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻
◻◼◼◻◻◻◻◻◻◼◻◻◻◻◻◻◻◻◻◻◻◻◻◼◻◻◻◻◻◼◼◻◻◻◻◻◼◼◻◻◻◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻
◻◻◼◼◻◻◻◻◼◼◼◻◻◻◻◻◻◻◻◻◻◻◼◻◼◻◻◻◻◼◼◻◻◻◻◼◼◻◻◻◻◻◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◻◻◻◻
◻◻◻◻◻◻◻◻◼◼◻◻◻◻◻◻◻◻◻◻◻◻◻◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◻◻◻◻
◻◻◻◻◻◻◻◻◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◻◻◻◻
◻◻◻◻◻◻◻◼◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻
◻◻◻◻◻◻◻◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻
◻◻◻◻◻◼◼◻◼◻◼◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻
◼◼◻◻◻◻◼◼◼◼◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻
◼◻◼◼◼◼◼◻◼◼◼◻◻◻◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻
◻◼◻◼◼◻◻◻◼◻◼◻◻◼◻◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◻◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◻
◼◼◻◻◻◼◻◻◻◼◻◻◻◻◼◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◻◼◻◻◻◻◻◻◻◻◻◻◻◻◼◼◻◻◻◻◻◻◻◻◻◼◻
◼◼◻◻◻◻◻◻◻◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◼◻◻◻◻◻◻◻◻◻◻◻◻◼◼◻◻◻◻◻◻◻◻◻◻◼
◻◼◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◼◻◼◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻
◻◻◼◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◼◼◻◼◼◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◼◻◻◻◼◼◻◻
◻◻◼◻◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◼◻◼◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◼◻◻◻◼◼◻◻
◻◻◻◼◼◻◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◼◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻
◻◻◻◼◻◻◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻
◻◻◻◻◼◼◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻
続いて、ライフゲームの表示をテキスト表示からCanvas APIによる描画へと切り替える。
Rendering to Canvas Directory from Memoryの記述のとおりにする。
src/lib.rs
、www/index.html
および www/index.js
を下記のものへと書き換えた。
src/lib.rs
は、impl Universe { ... }
に公開関数を3つ追加したのみ、www/index.html
は1行変更、www/index.js
は全面更新。
}
+ pub fn width(&self) -> u32 {
+ self.width
+ }
+ pub fn height(&self) -> u32 {
+ self.height
+ }
+ pub fn cells(&self) -> *const Cell {
+ self.cells.as_ptr()
+ }
pub fn new() -> Universe {
<body>
- <pre id="game-of-life-canvas"></pre>
+ <canvas id="game-of-life-canvas"></canvas>
<noscript>This page contains webassembly and javascript content, please enable javascript in your browser.</noscript>
import { Universe, Cell } from "wasm-game-of-life";
import { memory } from "wasm-game-of-life/wasm_game_of_life_bg";
// Constants for rendering to the canvas
const CELL_SIZE = 5; // px
const GRID_COLOR = "#CCCCCC";
const DEAD_COLOR = "#FFFFFF";
const ALIVE_COLOR = "#000000";
// Construct the universe, and get its width and height
const universe = Universe.new();
const width = universe.width();
const height = universe.height();
// Give the canvas room for all of our cells and a 1px border around each them
const canvas = document.getElementById("game-of-life-canvas");
canvas.height = (CELL_SIZE + 1) * height + 1;
canvas.width = (CELL_SIZE + 1) * width + 1;
const ctx = canvas.getContext('2d');
const getIndex = (row, column) => {
return row * width + column;
};
const drawGrid = () => {
ctx.beginPath();
ctx.strokeStyle = GRID_COLOR;
// Vertical Lines
for (let i = 0; i <= width; i++) {
ctx.moveTo(i * (CELL_SIZE + 1) + 1, 0);
ctx.lineTo(i * (CELL_SIZE + 1) + 1, (CELL_SIZE + 1) * height + 1);
}
// Horizontal Lines
for (let j = 0; j <= height; j++) {
ctx.moveTo(0, j * (CELL_SIZE + 1) + 1);
ctx.lineTo((CELL_SIZE + 1) * width + 1, j * (CELL_SIZE + 1) + 1);
}
ctx.stroke();
};
const drawCells = () => {
const cellsPtr = universe.cells();
const cells = new Uint8Array(memory.buffer, cellsPtr, width * height);
ctx.beginPath();
for (let row = 0; row < height; row++) {
for (let col = 0; col < width; col++) {
const idx = getIndex(row, col);
ctx.fillStyle = cells[idx] === Cell.Dead
? DEAD_COLOR
: ALIVE_COLOR;
ctx.fillRect(
col * (CELL_SIZE + 1) + 1,
row * (CELL_SIZE + 1) + 1,
CELL_SIZE,
CELL_SIZE
);
}
}
ctx.stroke();
};
const renderLoop = () => {
universe.tick();
drawGrid();
drawCells();
requestAnimationFrame(renderLoop);
};
drawGrid();
drawCells();
requestAnimationFrame(renderLoop);
実行すると、下記のように描画された。
Rust and WebAssemblyの4. Tutorialの残りを読んだ。
下記のようなことが書かれていた。RustやWasmよりJavaScriptの話の比率が多くなってきたため、ざっとだけ眺めた。
-
4.5. Testing Life
- Rustコードに対するテストの手順。
tests/*.rs
にテストコードを書いて、wasm-pack test --firefox --headless
などとすればよい
- Rustコードに対するテストの手順。
-
4.6. Debugging
- コンソール相当のものに文字列を出力する手順と、ブラウザの機能を使ってJavaScriptをステップ実行する手順
-
4.7. Adding Interactively
- pause/resumeボタンを足したり、canvas上をクリックするとセルの状態を反転できるようにするコード変更
-
4.8. Time Profiling
- 実行に要した所要時間の計測手順と、それを使ってコードを変更しながら実行時間を短縮していく様子
-
4.9. Shrinking .wasm size
- .wasmのサイズを縮小する方法 (ざっと紹介という程度だけ書かれている)
-
4.10. Publishing to npm
- npmパッケージの公開手順
これで、Rust and WebAssemblyは読了。