Closed47

Rustの学習

MasakiMasaki

手元のDebian bullseye (11.5) for amd64にRustの環境を構築した。

The book1.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にて確認)。

MasakiMasaki

The book1.2. Hello, World!
まず、cargoコマンドを使わずrustcコマンドだけでHello Worldをやってみる。

$ mkdir hello_world_without_cargo
$ cd hello_world_without_cargo

下記の中身のmain.rsを作成。

main.rs
fn main() {
    println!("Hello, world!");
}

rustcコマンドで実行バイナリを作成し、実行。意図通り動作した。

$ rustc main.rs
$ ./main
Hello, world!
MasakiMasaki

The book1.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プログラムになっている。

src/main.rs
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を使うことにする。

MasakiMasaki

The book2. 数当てゲームのプログラミング

先頭から順になぞって段階的にプログラムを書いて、最終的にリスト2-6のプログラムに至る。
下記を一通り浅く体験してみるといった感じ。

  • use宣言によるライブラリ読み込み
  • 不変変数と可変変数
  • String型による文字列
  • std::ioio::stdinによる1行入力
  • Result型によるエラー処理 (簡単なもの)
  • println!マクロの{}プレースホルダーによる値の表示
  • 疑似乱数生成
  • match式による場合分け
  • 変数のシャドーイング: 同名の新しい変数で古い変数を隠し、変数名を再利用すること
  • 文字列に対するtrim (空白などの削除)
  • 文字列に対するparse (数値への変換)
  • loopによるくり返し文、breakによる脱出、continueによる次のくり返しの開始
MasakiMasaki

The book3. 一般的なプログラミングの概念
下記が一通り堅く説明されている。コード片は出てくるが、まとまったプログラムを書く箇所はない。

  • 変数と可変性
  • データ型
    • スカラー型: 整数型、浮動小数点数型、論理値型、文字型
    • 複合型: タプル型、配列型
  • 関数
    • 引数
    • 本体
    • 戻り値
  • コメント
  • 制御フロー
    • if
    • ループ
      • loop: 無条件ループ
      • while: 条件付きループ
      • for: コレクションの要素に対するループ
    • ループの制御
      • break: ループからの脱出
      • continue: 次のループに移る

isizeusizeについては正確な説明がなかったので、下記を参照した。

  • 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.
  • 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.

配列を含むコレクションに対するサイズはusize、インデックスはisizeと思えばよさそう。
2022-10-11訂正: 配列を含むコレクションに対するサイズとインデックスはusize、インデックスの差分はisizeと思えばよさそう。

MasakiMasaki

The bookの3章までの学習内容で、フィボナッチ数列を表示するプログラムを書いて動作させてみる。

$ cargo new fibonacci
$ cd fibonacci

src/main.rsに下記を書く。

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ループ
MasakiMasaki

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)を表現できる
    • mainResultを返せる
    • ?演算子を使うと、エラー処理を簡潔に書ける
    • OptionResultunwrapは、panic!で止めてよいコードを書くには便利
MasakiMasaki
  • 第5章 - データの所有権と借用
    • スコープの終わりでのデストラクトと解放のことをドロップと呼ぶ
    • 構造体のドロップは、構造体が先で要素が後 (意外だった)
    • 変数を関数の実引数に渡すと、所有権が関数の仮引数に移動する
    • 戻り値で所有権を渡せる
    • &演算子でリソースへのアクセスを借用できる
    • &mut演算子でリソースへの変更可能なアクセスを借用できる
    • *演算子で参照を外せる (dereference)
    • ライフタイムは理解しきれなかった。スキップ
  • 第6章 - テキスト
    • r#" "# で生文字列リテラルを書ける
    • 文字列リテラルに対するlenは文字数ではなくバイト数を返す
    • Stringはヒープ上に配置されたutf-8バイト列
    • concatjoinで文字列を連結できる。concatは区切りなし、joinは区切りあり
    • format!{}で、println!のようなことを文字列で行える
    • 多くの型はto_stringで文字列へと変換できる。parseはその逆
MasakiMasaki

Rustツアー第7章 - オブジェクト指向プログラミングを読み進めている。題名はオブジェクト指向プログラミングだが、主題はトレイト。

Influences - The Rust Programming LanguageWhy Rust? - #Influences | Learning Rustによれば、RustのトレイトはHaskellの型クラスを持ち込んだものと思えばよさそうだ。C++のtype_traitsとは関係がない。
(参考: Rustに影響を与えた言語たち)

MasakiMasaki

ほかの言語などから来ているであろうものについて、もう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
MasakiMasaki
  • 第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が満たすトレイトを指定できる
    • データをスタックではなくヒープに置くには、スマートポインタの1つであるBoxを使う
MasakiMasaki

8章からは和訳がないので英語版を使う。

  • Chapter 8 - Smart Pointers
    • *const T*mut TはT型への生ポインタ。C言語のポインタとほぼ同じもの。参照外しはunsafeコードでのみ使用できる
    • Rustの参照は、実行時の振る舞いはポインタと等価。ただし、コンパイル時に多くの保護が課される
    • .演算子は、C言語のものと同様のメンバ参照演算子。ただし、左辺値がポインタあるいは多段ポインタの場合は、参照外しを暗黙に行う
    • スマートポインタの実装などでの生ポインタの参照外しは、unsafeコード内で行う
    • std::allocallocLayoutは、C言語のmallocと同様のことを行う (詳しいことは書かれていない)
    • Boxは、ヒープ上に値を保持するシンプルなスマートポインタ
    • std::rcRcは、参照カウント方式のスマートポインタ
    • RefCell, Mutex, Arcは、ツアーだけではよくわからなかった。スキップ
MasakiMasaki
  • 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ツアーは完了。結局読むばかりになってしまった。

MasakiMasaki

The bookと眺め終わったRustツアーのそれぞれの目次を見比べて、追加で学習したい箇所の候補をリストアップ。

  • Rustツアーがカバーしている
    • 変数、基本的な型、関数
    • 制御フロー
    • 配列、タプル
    • 構造体、列挙型
    • Generics
    • OptionResult
    • 所有権、借用、参照
    • 文字列
    • トレイト
    • Box
    • 生ポインタ
    • モジュールとクレート
  • Rustツアーだけでは理解が不十分
    • エラー処理
    • ライフタイム
    • スマートポインタ
  • Rustツアーにない
    • パッケージ
    • コレクション
    • 自動テスト
    • 入出力
    • イテレータとクロージャ
    • Cargo
    • crates.io
    • 並行性
    • マクロ

下記を参考に:

改めて参考文献を洗い出した。

組み込み関連の機能は、興味があるので把握したい。WebAssemblyは、現時点では知識がなく使うかどうかわからないので、概要を拾っておきたい。

ということで、下記の順序で読み物を読んでいこうと思う。

  1. The Bookを読み返して基礎を固める
  2. 組み込み関連のものを通しで読んで、機能を把握する
  3. WebAssembly関連のものをざっと眺める

演習が足りないと感じた場合には、Command Line Applications in Rustを参考にCLIな何かを作ってみるのもいいかもしれない。

初学者の段階を抜けたら、実践的な技術の参考文献をRustの良質な学習リソースをまとめるから改めて拾うのがよさそう。

MasakiMasaki

The book7. 肥大化していくプロジェクトをパッケージ、クレート、モジュールを利用して管理するを読んだ。わかりにくいと感じた箇所は、下記も参考にした。

以下、理解したつもりのことのメモ。

  • モジュール: 関数、構造体、トレイトなどをひとまとまりにして、そのまとまりに名前をつけたもの
    • mod my_module { fn my_func () {} }と書くと、my_func関数を含むmy_moduleモジュールが定義される
    • モジュール内にモジュールを置くことができる。これらのモジュールは全体で階層構造(モジュールツリー)をなす
  • クレート: コンパイラにとっての翻訳単位。別々のクレートは別々にコンパイルされる
    • 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::tonameが持ち込まれる)
    • use path::to::name as nickname;と書くと、以降の現在のスコープ内でnicknameと書くとpath::to::nameを指すようになる
    • use path::to::module::{A, B};と書くと、現在のスコープにABが持ち込まれる
    • use path::to::module::*;と書くと、現在のスコープにpath::to内のすべての公開要素が持ち込まれる (*をglob演算子と呼ぶ)
  • モジュールツリーを複数のファイルに分割する手順
    • 以下の2通りの記述は等価
      • src/main.rsmod my_module { pub fn my_func () {} }と書く
      • src/main.rsmod my_module;と書き、src/my_module.rspub fn my_func () {}と書く
  • パッケージ: Cargoによる管理単位
    • 1つのパッケージは1つのCargo.tomlを持つ
    • パッケージは1つ以上のクレートを持つ
      • パッケージに含まれるライブラリクレートの個数は0個または1個
      • パッケージに含まれるバイナリクレートの個数は何個でもよい

Cargoはパッケージだけでなくワークスペースも扱うが、後回しにする。

MasakiMasaki
  • 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 { ... }ivの各要素の値
      • 全要素の走査その2: 例えばfor i in &v { ... }ivの各要素の不変参照
      • 全要素の走査その3: 例えばfor i in &mut v { ... }ivの各要素の可変参照
      • 参考文献
    • String: 文字列型
      • Stringは文字列を表す型
        • 一方、let s = "hello";sは文字列スライスで、Stringと文字列スライスは別のもの
      • StringVec<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 { ... }keyvaluemapの各要素の値
      • 全要素の走査2: 例えばfor (key, value) in &map { ... }keyvaluemapの各要素の不変参照
      • 参考文献
    • 所感: コレクションに関しては、The bookを読むよりもRust by Exampleの例を眺めるほうが理解しやすいように感じる
  • 9. エラー処理
    • 回復不能なエラーに対してはpanic!を用いる
    • 回復可能なエラーに対してはResult<T, E>を用いる。そののち、下記のいずれかを使う:
      • 手書きで場合分けを記述するならmatch
      • エラー時に単にpanic!させるならunwrap
      • エラー時に所定の文字列でpanic!させるならexpect
      • エラー時に呼び出し元にエラーを返すなら?
MasakiMasaki
  • 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ディレクトリ内のファイルは、テスト時のみビルドされる

導入としては上記ぐらいで十分と思うが、実用的なテストを書く際には下記などを参考に情報収集したほうがよさそう。

MasakiMasaki

#[test]#cfg[test]などが出てきて気になったので、属性(attribute)について軽く調べた。

  • Attributes - The Rust Reference
    • 言語としての構文の定義、ビルトインの属性の一覧などがある。調べるときには、まずここを見ればよさそう
      • 属性の構文は、InnerAttribute #![Attr] と OuterAttribute #[Attr] のいずれか
      • 属性の種類は下記のとおり
        • Built-in attributes
        • Macro attributes
        • Derive macro helper attributes
        • Tool attributes
  • アトリビュート - The Rust Example日本語版
    • いくつかの例あり。現時点では、下記が記載されている
      • #[allow(dead_code)]
      • #[crate_type = ...]#[crate_name = ...]
      • #[cfg(...)]
MasakiMasaki

The book12. 入出力プロジェクト:コマンドラインプログラムを構築する
minigrepという小さなgrepプログラムの実装を通して、下記を扱う。

  • コードの体系化
  • ベクタ
  • 文字列
  • エラー処理
  • トレイト
  • ライフタイム
  • テスト

順に読みながらソースコードを更新していって、できあがったソースコードは下記のとおり。

main.rs
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);
    }
}
lib.rs
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 で十分

コードの体系化・エラー処理・テストは、何となく感触が得られた気がする。特に、エラー処理の仕組みはすっきりしていてよい。他の言語でよく使われる例外処理のような、処理系の実装がとても複雑なものを使わないで済むのがよい。

文字列は、様子見した程度という印象。例えば、runcontents: Stringsearch(..., &contents) とするだけで searchcontents: &str に渡せる仕掛けは、まだ理解できていない。

ベクタ・トレイト・ライフタイムは、ちょっと出てきた程度なので、理解が進んだ感触はない。

また、あまり強調はされていなかったが、所有権の扱いが印象に残った。例えば、search 関数で使っている lines 関数は、コピーではなく参照を返している。所有権の扱いが明確な分、コピーを作成する場面が少ない。

MasakiMasaki

文字列と文字列スライスの理解が浅いので復習。

runcontents: Stringsearch(..., &contents) とするだけで searchcontents: &str に渡せる仕掛けはまだ理解できていない」と書いたが、The book4.3. スライス型の中盤に、下記のように書いてあった。

  • 関数の引数の型を文字列スライス型の &str にすると、&String 型と &str 型の両方に使える
MasakiMasaki
  • 13. 関数型言語の機能: イテレータとクロージャ
    • クロージャ
      • let f = |arg1, arg2| { arg1 + arg2 }; などと書くと、定義したクロージャが変数 f に保持される
        • この例のように、クロージャ内の式が1つしかないなら、 { ... } は省略してもよい
        • let f = |arg1: u32, arg2: u32| -> u32 { arg1 + arg2 }; などと型注釈を明示してもよい
      • クロージャに関する標準のトレイトは下記の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()

イテレータに関しては、下記が参考になった。

MasakiMasaki
  • 14. CargoとCrates.ioについてより詳しく
    • ざっと眺めた。すぐには細部を使わない気がしたため流し読みをしただけで、メモは特になし
  • 15. スマートポインタ
    • Box<T> は、ヒープ上に値を保持するシンプルなスマートポインタ
    • Deref トレイトは、スマートポインタを通常の参照のように扱えるようにする
    • Drop トレイトは、値がスコープから外れる箇所で所定の処理を行わせられるようにする
    • Rc<T> は、参照カウント方式のスマートポインタ。複数の所有権を扱える
    • RefCell<T> は、借用規則の適用を実行時に行わせられるようにするスマートポインタ
MasakiMasaki
  • 16. 恐れるな!並行性
    • スレッド
      • let handle = thread::spawn(|| { ... }); などとすると、新しいスレッドを生成できる (use std::thread; が必要)
      • handle.join().unwrap(); などとすると、スレッドの終了を待つことができる
    • メッセージ受け渡し (Message Passing)
      • let (tx, rx) = mpsc::channel(); などとすると、送信チャンネル tx と受信チャンネル rx を生成できる(use std::sync::mpsc が必要)。txrx は、別のスレッドに所有権とともに渡してよい
      • 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種類の手続き的マクロがあるとのこと
    • 宣言的マクロは、他の言語で一般に言われるところのマクロにあたる。マクロの呼び出しは、展開されたコードを手書きするのと等価な意味を持つ
    • 手続き的マクロについては、読んだがいまいちよくわからず
MasakiMasaki

これで、10月22日に書いた下記のリストのうち、The Bookまでは完了した。

  1. The Bookを読み返して基礎を固める
  2. 組み込み関連のものを通しで読んで、機能を把握する
  3. WebAssembly関連のものをざっと眺める

組み込み関連とWebAssembly関連のものとして、次は下記を読む。

また、下記の概要を知っておきたい。

  • ドキュメンテーション
  • Rustのバージョン間の差異

これらが済んだら、初学者レベルの学習は完了でよいだろう。

MasakiMasaki

先にバージョン(というよりもエディション)とドキュメンテーションについて調べた。

  • エディション
    • 参考資料: エディションガイド (原文: The Rust Edition Guide)
    • Rustには、ツールのバージョンとは別に、エディションというものがある。例えば、Rust 2015, Rust 2018, Rust 2021 などはエディションである。エディションが異なると、文法に差異があり、新しいものは後方互換性がないこともある
    • エディションはクレートごとに選択できる(Cargoの設定などにて)。エディションの異なるクレートを混在させてビルドできる
    • rustcなどのRustツールは、すべてのエディションをサポートし続ける。例えばrustcを更新したとしても、Rust 2015は引き続きサポートされる
    • 各エディションの概要は下記のとおり
      • Rust 2015: 最初のエディション
      • Rust 2018: モジュールシステムの改良などが行われた
      • Rust 2021: Preludeへの機能追加などが行われた
  • ドキュメンテーション
    • 参考資料: The bookの14.2Rust 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)
MasakiMasaki

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
MasakiMasaki

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であることを確認。

.cargo/config.toml
...
[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 = "... の行を下記のようにアンコメントすればよい。

.cargo/config
[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!
MasakiMasaki

デバッガ経由での実行を試す。

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を完了させる。

MasakiMasaki

1. 導入2. 入門のインストール以外の記述を読んだ。

  • 1.1. no_std
    • #![no_std]という記述を含むクレートは、stdクレートではなくcoreクレートとリンクされる。この場合、OSのサポートを受けられない
  • 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-abortpanic-haltpanic-semihostingなどから1つだけ選んで指定する必要がある
  • 2.6. 例外
    • 実行環境のプロセッサのための例外ハンドラを指定できる。例えばCortex-Mシリーズであれば、cortex_m_rtクレートexception属性を使うことで、例外ハンドラを指定できる
  • 2.7. 割り込み
MasakiMasaki
  • 3. ペリフェラル: メモリマップド・ペリフェラルの扱い方
    • 構造体を使ってメモリマップドレジスタを正確に表現するには、その構造体に#[repr(C)]修飾子を付ける
    • C言語で言うところのvolatile修飾子を使うには、下記のいずれかを使用する
    • 物理的に1つ(あるいは限られた個数)しかないペリフェラルの状態をあちこちから参照・変更することを防ぐために、インスタンスの個数を借用チェッカを使って管理するとよい (参照: 3.3. シングルトン)
  • 4. 静的な保証、およびゼロコスト抽象化
    • Rustのビルダーパターンを使うことで、例えば操作開始前に必ず初期化しなければならないことを静的に強制できる。この制約は型を使って表現されるため、コンパイル時にチェックされ、実行時コストがかからない
    • struct Enabled;などとすると、サイズがゼロの型を定義できる。この型は、コンパイル時には他の型と同様に扱われるが、最適化で消去されるため、実行時にはコストがかからない
MasakiMasaki
  • 5. 移植性
    • embedded_halクレートのような抽象化を挟むことで移植性を確保する話。書かれていることは概略のみ
  • 6. 並行性
  • 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に似ているが、最初に容量を指定する必要があるなどの差異がある
  • 8. 組込みC開発者へのヒント
    • 組み込み用途のC言語で行っていたことをRustでどう扱うのかのヒント集。他の章のおさらいのようなもの
  • 9. 相互運用性
    • RustからCコードを使う方法、その逆、ビルドシステムの都合のつけ方など

これでThe Embedded Rust Book和訳は読了。

MasakiMasaki

rustupとcargoについて、改めて調べた。すでに両方を使ってみているのでおおよそはわかるのだが、どちらにも「パッケージマネージャ」という役割がついていて境界線がいまいちしっくりきていなかったため。

  • rustup
    • 参考文献: The rustup bookConcepts
    • rustupは、Rust公式のパッケージマネージャである。下記のようなものを管理する
      • ツールチェイン: rustcやcargoなど
      • コード生成対象のターゲットプラットフォーム: native、クロスビルドなど
    • rustupをインストールするには、Unix環境では curl https://sh.rustup.rs -sSf | sh を実行すればよい。このコマンドは、rustup-init.sh というシェルスクリプトをダウンロードし、引数なしで実行する。これにより、rustupがデフォルトのインストール先である ~/.cargo/bin へとインストールされる
  • cargo
    • 参考文献: The cargo bookWhy Cargo Exists
    • cargoは、Rust公式のビルドシステム兼パッケージマネージャである。下記のようなことを行う
      • ユーザが記述したパッケージのビルドや実行
      • 依存するパッケージの取得やビルド
    • rustupを使っている場合には、cargoのインストールはrustupが行う

両者の差異と共通点については、下記が参考になった。

MasakiMasaki

Rust and WebAssemblyを読むにあたり、まずWasmとは何かを調べた。参考にしたものは下記。

下記のように理解した。

  • WebAssembly(Wasm)は、スタック方式の仮想マシンの命令セットである
  • Wasmは、下記を行いやすいよう設計されている
    • 様々なプログラミング言語からWasmへとコンパイルしやすい
    • Web上のクライアントやサーバで実行しやすい
  • Wasmは高速に実行できる
    • .wasmはバイナリフォーマットのため、仮想マシンで実行する前の事前準備がほぼ不要 (JavaScriptは字句解析や構文解析を要する)
    • Wasmの仮想マシンは、実行効率を重視して設計されている
  • Wasm 1.0は、主要な4つのブラウザ: Firefox、Chrome、Safari、Edge でサポートされている
MasakiMasaki

Rust and WebAssembly和訳を読んでいる。

  • 2. なぜRust and WebAssembly?
    • Webアプリで使われるJavaScriptは、処理性能が遅いことが課題。原因は、動的型付けとガベージコレクション
    • Rustは、静的型付けで、かつガベージコレクションを必要としない。また必須のランタイムコードがないため、コードサイズが小さく、Web経由でのロードが軽量で済む
    • Rust + Wasmは、JavaScriptとうまく組み合わせられる。既存のJavaScript資産の一部分だけを置き換えるなどが容易
  • 3. 背景とコンセプト
    • Wasmとは何か: すでに調べてあるのでスキップ
    • WasmはWebだけのためのものか: そういう仕様制約はないが、目下の用途はWebである
MasakiMasaki

Rust and WebAssembly和訳4. チュートリアルを行う。
まずセットアップの記述にしたがい、下記のものをセットアップする。

  • Rustツールチェイン、cargo-generate
  • 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がよろしく扱ってくれるようなので:

書かれているとおりにした。

$ 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`)
MasakiMasaki

Rust and WebAssembly4. Tutorial4.2. Hello, World!を行う。
なお、和訳4.2. Hello, World!の途中までしか翻訳されていない様子だったため、以降は原文を直接読むことにした。

流れは下記のとおり。

  1. wasm-pack-templateをベースに、wasm-game-of-lifeという名前のプロジェクトを作成
  2. ビルド
  3. 実行用のWebページをローカル環境に新規作成
  4. そのWebページにビルドしたプロジェクトをインストール
  5. 実行
  6. 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 で停止させた。

MasakiMasaki

Rust and WebAssembly4.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-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を使うときに利用すると便利なテンプレート
MasakiMasaki

もっとシンプルなWasm実行を試しておきたかったので、いくつか調べた。

下記のように理解した。

  • Wasm Coreは、仮想マシンの命令セットのみを定義したもの。入出力機能は別途足す必要がある
  • Wasm CoreにWASIとそのランタイムサポートを足せば、通常のOSの機能を介してファイル入出力などを行えるようになる
  • WASIのランタイム実装のひとつがWasmtime
  • Wasm Coreにwasm-bindgenを足せば、Webブラウザ・Node.js・JavaScriptを介して入出力を行えるようになる

RustでWASIを扱う場合は、cargo-wasiを使うのが手早そうに見えたので、下記の順序でやってみた。

  1. cargo-wasiをインストール
  2. wasmtimeをインストール
  3. Hello worldプログラムを作成
  4. ビルド
  5. 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
...
MasakiMasaki

Rust and WebAssembly4.4. Implementing Lifeをなぞって、ライフゲームを作る。
Rustのプロジェクトは、12月24日に作成したwasm-game-of-lifeをそのまま使う。

src/lib.rswww/index.html および www/index.js を下記のものへと書き換えた。

src/lib.rs
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()
    }
}
www/index.html
<!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>
www/index.js
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 installwasm-game-of-life を更新。
...と思ったが、うまく更新されなかったので、下記のように通常のcpコマンドで上書きした。

cp -f ./pkg/* ./www/node_modules/wasm-game-of-life

./www にて npm run start を実行し、Webブラウザで http://localhost:8080 を開くと、テキスト表示のライフゲームが動作した。
ある状態でのスクリーンショットは下記。実際には、ウェイトなどが入っていないため状態がどんどん遷移していく。

◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻
◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻
◻◻◻◻◻◻◼◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◼◻
◻◻◻◻◻◼◻◻◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◻◻◼
◻◻◻◻◻◻◼◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◻◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◼◻
◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◻◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻
◻◻◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◻◻◻◻◻◻◻◻◻◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻
◻◼◻◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻
◻◼◻◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻
◻◻◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻
◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻
◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◻◻◻◻◻◻◻◻◻◼◼◻◻◻◻◼◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻
◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◻◻◻◻◻◻◻◻◼◻◻◼◻◻◼◻◻◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻
◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◼◻◻◻◼◻◼◻◻◻◻◻◻◻◼◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻
◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◻◻◻◻◻◻◻◼◻◻◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻
◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻
◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻
◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻
◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻
◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻
◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◻◻◻◻◻◻◻
◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◼◻◼◻◻◻
◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◼◻◻◻◼◼◻◼◻◻◻
◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◻◻◼◼◻◻◻◼◻◻◻
◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◼◼◻◻◻◻◼◼◻◻◻
◻◼◼◻◻◻◻◻◻◻◻◻◼◻◻◻◻◻◻◻◻◻◻◻◼◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◼◼◼◻◻◻◻◻◻
◻◼◼◻◻◻◻◻◻◻◻◻◼◻◻◻◻◻◻◻◻◻◻◼◻◻◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◼◻◻◻◻◻◻◻◻◼◻◻◻◻◼◼◻◻◻
◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◻◻◼◻◻◻◻◻◻◻◻◼◻◻◻◻◻◻◻◻
◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◻◼◻◻◻◻◻◻◻◻◻◼◻◻◻◻◻◻◻
◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻
◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻
◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻
◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◻◻◻◻◻◻◻◻◻◻◻◻◼◻◻◻◻◻◻◻
◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◼◻◻◻◻◻◻◻◻◻◻◼◻◼◻◻◻◻◻◻
◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◻◻◼◻◻◻◻◻◻◻◻◻◼◻◼◼◻◻◻◻◻
◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◻◼◻◻◻◻◻◻◻◻◻◼◻◼◼◻◻◻◻◻
◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◼◻◻◻◻◻◻◻◻◻◻◻◼◼◼◻◻◻◻◻
◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◻◻◻◼◻◻◻
◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◻◻◻
◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◼◻◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◻◻◻
◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◻◼◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻
◻◻◻◻◻◻◻◻◻◻◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◻◻◻◼◻◻◻◻◻◻◻◻◻◻◻◻◻◼◻◻◻◻
◻◻◻◻◻◻◻◻◻◼◻◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◼◻◼◼◻◻◻◻◻◻◻◻◻◻◼◻◻◻◻◻◻
◻◻◻◻◼◻◻◻◻◻◼◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◼◼◼◻◻◻◻◻◻
◻◻◻◼◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◼◼◻◻◻◻◻◻◻
◻◻◼◻◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◻◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻
◻◼◼◻◻◻◻◻◻◼◻◻◻◻◻◻◻◻◻◻◻◻◻◼◻◻◻◻◻◼◼◻◻◻◻◻◼◼◻◻◻◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻
◻◻◼◼◻◻◻◻◼◼◼◻◻◻◻◻◻◻◻◻◻◻◼◻◼◻◻◻◻◼◼◻◻◻◻◼◼◻◻◻◻◻◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◻◻◻◻
◻◻◻◻◻◻◻◻◼◼◻◻◻◻◻◻◻◻◻◻◻◻◻◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◻◻◻◻
◻◻◻◻◻◻◻◻◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◻◻◻◻
◻◻◻◻◻◻◻◼◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻
◻◻◻◻◻◻◻◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻
◻◻◻◻◻◼◼◻◼◻◼◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻
◼◼◻◻◻◻◼◼◼◼◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻
◼◻◼◼◼◼◼◻◼◼◼◻◻◻◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻
◻◼◻◼◼◻◻◻◼◻◼◻◻◼◻◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◻◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◻
◼◼◻◻◻◼◻◻◻◼◻◻◻◻◼◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◻◼◻◻◻◻◻◻◻◻◻◻◻◻◼◼◻◻◻◻◻◻◻◻◻◼◻
◼◼◻◻◻◻◻◻◻◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◼◻◻◻◻◻◻◻◻◻◻◻◻◼◼◻◻◻◻◻◻◻◻◻◻◼
◻◼◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◼◻◼◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻
◻◻◼◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◼◼◻◼◼◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◼◻◻◻◼◼◻◻
◻◻◼◻◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◼◻◼◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◼◻◻◻◼◼◻◻
◻◻◻◼◼◻◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◼◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻
◻◻◻◼◻◻◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻
◻◻◻◻◼◼◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻
MasakiMasaki

続いて、ライフゲームの表示をテキスト表示からCanvas APIによる描画へと切り替える。
Rendering to Canvas Directory from Memoryの記述のとおりにする。

src/lib.rswww/index.html および www/index.js を下記のものへと書き換えた。
src/lib.rsは、impl Universe { ... } に公開関数を3つ追加したのみ、www/index.htmlは1行変更、www/index.js は全面更新。

src/lib.rs
     }
+    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 {
www/index.html
   <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>
www/index.js
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);

実行すると、下記のように描画された。

game-of-life-canvas-screenshot

MasakiMasaki

Rust and WebAssembly4. Tutorialの残りを読んだ。
下記のようなことが書かれていた。RustやWasmよりJavaScriptの話の比率が多くなってきたため、ざっとだけ眺めた。

  • 4.5. Testing Life
    • Rustコードに対するテストの手順。tests/*.rs にテストコードを書いて、wasm-pack test --firefox --headless などとすればよい
  • 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は読了。

MasakiMasaki

Rustでのドキュメンテーションについて。

12月4日にだいたい調べたとおりだが、追加で下記を読んだ。

また、実例として下記を拾っておいた。

前者はstd::optionのソースコード、後者はそのソースコードから生成されたドキュメント(のはず; 細かいバージョン差異があるかもしれない)。実際にドキュメントを書く際には、細部を参考にしながら書けばよさそう。

Rustの学習は、ここまでで一区切りとする。次に何をするのがよいのかをまだ考えていないが、できれば何か作ってみたい。

このスクラップは2022/12/29にクローズされました