Last active
April 5, 2025 13:17
-
-
Save mizchi/f58ce92b4802c938973f738e29d76a59 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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