Skip to content

Instantly share code, notes, and snippets.

@mizchi
Last active May 7, 2022 14:25
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mizchi/f58ce92b4802c938973f738e29d76a59 to your computer and use it in GitHub Desktop.
Save mizchi/f58ce92b4802c938973f738e29d76a59 to your computer and use it in GitHub Desktop.
use wasm_bindgen::prelude::Closure;
// あとで使う as_unchecked_ref は JsCast によって定義される trait
use wasm_bindgen::{JsCast, JsValue};
fn main() -> Result<(), JsValue> {
// クリックすると値が増えるだけのカウンタを作る
let window = web_sys::window().unwrap();
let document = window.document().unwrap();
let body = document.body().unwrap();
let el = document.create_element("button").unwrap();
let mut cnt = 0;
el.set_text_content(Some(&format!("{}", cnt)));
body.append_child(&el).unwrap();
// handler は move で中で使う参照を持っていってしまうで
// add_event_listener の中で使うための DOM 参照を clone しておく
let el_inner = el.clone();
// Rust の Closure は実際にはキャプチャする対象を含めた構造体を生成して、それを引き回す
// なのでメモリ量を示すための返り値を直接記述することが難しい
// Box で囲うことで as Box<dyn FnMut()> でき、これがポインタとしてメモリ量を固定する
// この参照を色々と引き回して JS に渡すことになる
let handler = Box::new(move |_event: web_sys::MouseEvent| {
cnt += 1;
el_inner.set_text_content(Some(&format!("{}", cnt)));
}) as Box<dyn FnMut(_)>;
// handler を js の世界に伝えるためのクロージャを作る
// これは次のような構造体になっている
//
// pub struct Closure<T: ?Sized> {
// js: ManuallyDrop<JsValue>,
// data: ManuallyDrop<Box<T>>,
// }
// JsValue は js の世界に渡される Rust(Wasm) の関数テーブルを指す値で、
// それを使うと wasm のリニアメモリ上の関数実体(data)が解決できる
let closure = Closure::wrap(handler);
// closure.as_ref() で JsValue 型の JS の世界に渡すポインタを取得する
// unchecked_ref() で js_sys::Function できるように cast してJS側に投げ込む。ここは実装上必要なある種のおまじない
// Rust のコードだけを読むだけではわからないが、実際には、JS 側のコードでコールバックの注入と、実行時の関数テーブルの解決が行われ、
// 結果として handler が呼ばれることになる(という理解を自分はしている)
el.add_event_listener_with_callback("click", closure.as_ref().unchecked_ref()).unwrap();
// 最後の処理
// Rust の世界の handler は、本来ならこのスコープを抜けるときに自動的に drop されて消えてしまうが、
// std::mem::forget を呼ぶことで Rust のライフタイム管理から外し、解放されないようになる
// ある種の意図的なメモリリークを行っている
closure.forget();
// その実装を追うとこんな感じ
//
// pub fn into_js_value(self) -> JsValue {
// let idx = self.js.idx;
// mem::forget(self);
// JsValue::_new(idx)
// }
// /// Same as `into_js_value`, but doesn't return a value.
// pub fn forget(self) {
// drop(self.into_js_value());
// }
// もし破棄したいときは、この closure を引き回して、不要になったときに drop する
Ok(())
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment