地道に進めています。わかってくると Rust おもしろいですね。The Programing Language Rust を読んだほうが良い気がしてきましたが・・・
unwrap p13
本の中では unwrap がふたつあるunwrap().unwrap()ことについて解説がありますが、そもそもunwrapを使っている理由が書いてありません。まず unwrap とは:
- None に対して unwrap するとパニックする
- Some (値がある)に対して unwrap するとその値を返す
window.document() が返すのは Option<Document> なので、Document オブジェクトではないというのがポイント。これは NULL が帰る可能性を示唆していて、Rust としてはコンパイラに型エラーを任せるための大事な祖h里ですが、コーディングする側としては Option から Document を取り出さないとならんわけです。型安全なシステムならではの手間ですね。安心感とめんどくささが相まってなんともたまりません。
Canvas へのスプライト描画 p29
三角形の描画の後は、描画方法の話なので画像の表示まで進みます。
ここから画像をコピーしないといけないのですが、build がなんだかうまく行かなくなってしまいました。
- 画像をどこに置くかわからない
- walk-the-dog.js が作られなくて index.js になってる
これに対応するため、webpack.config.js で static ディレクトリを dist にコピーするようにします。
plugins: [
new CopyPlugin({
patterns: [{ from: "static" }],
}),
site/static ディレクトリを作成して、そこに index.html を移動します。画像ファイルもここに置いていきます。
site/index.js で import するファイル名を変更しておきます。
import("../pkg/index.js").catch(console.error);
これで解決。
イミュータブルなのに set_src?
let image = web_sys::HtmlImageElement::new().unwrap();
image.set_src("Idle (1).png");
こんなソースが使われているが、なんだか set_src がセッターに見えます。イミュータブルな image オブジェクトの値を変更しているのでは?と思いきや、この image は js のオブジェクトに対するポインタなので Rust の変数を変更してるわけではないから問題ないそうです。
あの有名な借用チェッカ
といわれても初心者は何も知りません。Rust は明示的にメモリの所有権をセットする必要があり、それがメモリ安全かどうかをコンパイラがチェック(借用チェック)するそうです。The Rust Language Programmingがわかりやすかったです。
fn main() {
{
let s1 = String::from("hello"); // s1がメモリを確保(所有権を持つ)
} // s1のスコープ終了。ここで自動的にfree(解放)される。
// print!("{}", s1); // エラー! s1はもう存在しない。
}
所有権を移動して、s1 を使えないようにすることでダブルフリーなどを防止できます。
fn main() {
let s1 = String::from("hello");
let s2 = s1; // 所有権が s2 に移動
// println!("{}", s1); // コンパイルエラー!
println!("{}", s2); // s2は使える。
} // s2がスコープを抜けるので、ここでメモリが解放される。
借用は、所有権を渡さずその変数へのポインタを新たに作成します。r1 -> s -> "hello" という感じ。
fn main() {
let s = String::from("hello");
let r1 = &s; // 貸し出し1(読み取り専用)
let r2 = &s; // 貸し出し2(読み取り専用)
println!("{} and {}", r1, r2); // 同時に複数人で読むのは安全
}
書き込みは atomic というか1つだけだそうな。
fn main() {
let mut s = String::from("hello");
let r1 = &s; // 読み取り用に貸し出し中
let r2 = &mut s; // エラー!「読み取り中なのに書き換えられると困る」と借用チェッカが怒る
}
クロージャ
さらっとクロージャを使った例が出てきます。image の on_load イベントに無名関数をハンドラとして渡そうとしているようです。クロージャは、引数を渡さなくて良いところが特徴ですね。
fn main() {
let x = 10;
// これがクロージャ。外側の変数 `x` を使っている。
let add_x = |y| y + x;
println!("{}", add_x(5)); // 15
}
futures::channel::oneshot::channel::<()>()
シンボルが連なっていていわかりにくいですが、futures のチャネル機能(非同期タスク間のデータ通信)を作成する関数。問題は、<()> でしょう。これは、チャネルに対して渡すデータ型を定義するものでターボフィッシュというそうです。<i32> とすると、tx, rx は引数に i32 を取ることになる。ここでは、image の on_load イベントが発生したところで、context をアップデートする処理を wakeup させるだけなので、なにかデータを渡す必要はないので <()> (ユニット型:中身が何も無い型)を指定します。
Mutex
これが難しかった・・・
if let Some(success_tx) = success_tx.lock().ok().and_then(|mut opt| opt.take()) {
success_tx.send(Ok(())).unwrap();
};
たたでさえ長いのが、vscode が型名を自動で表示するのですごく長くなって意味がわからない状態になり、知らない関数もたくさんで戸惑いました。ここまでで Rust の戻り値が Result みたいに2つの型を持つことがあることを学びましたが、これはさすがに。整理してみます。
- 型
- opt.take() の戻りが Option
- success_tx.lock() の戻りが Result
- 取り出す関数
- ok() が Result から
- Some() が Option から
- and_then() は戻り値が空じゃなければ次の処理に渡す
and_then() は if 文に置き換えられるので、以下のような関数を作ることが可能です。
fn wakeup_channel(
tx_anchor: Rc<Mutex<Option<oneshot::Sender<Result<(), JsValue>>>>>,
result: Result<(), JsValue>,
) {
if let Ok(mut guard) = tx_anchor.lock() {
if let Some(tx) = guard.take() {
let _ = tx.send(result);
}
}
}
これで callback のコードがすっきり。ワンライナーにこだわるか、見やすくするか、初心者の私には後者がちょうどいい感じです。
let callback = Closure::once(move || {
wakeup_channel(success_tx, Ok(()));
});
let error_callback = Closure::once(move |err| {
wakeup_channel(error_tx, Err(err));
});
ところで Rc もよくわからず調べたところ、Reference count の略で所有者がどれだけいるかカウントする機能でした。参照は read only なので、& で借用するのに似てますが、Reference count は参照する処理が残ってる限り使い続けることができるそうです。マルチスレッドでは使えないので注意。