web-sys: Closures - The wasm-bindgen
Guide (original) (raw)
- Introduction
- 1. Examples
- 1.1. Hello, World!
- 1.2. Using console.log
- 1.3. Small Wasm files
- 1.4. Without a Bundler
- 1.5. Synchronous Instantiation
- 1.6. Importing functions from JS
- 1.7. Working with char
- 1.8. js-sys: WebAssembly in WebAssembly
- 1.9. web-sys: DOM hello world
- 1.10. web-sys: Closures
- 1.11. web-sys: performance.now
- 1.12. web-sys: using fetch
- 1.13. web-sys: Weather report
- 1.14. web-sys: canvas hello world
- 1.15. web-sys: canvas Julia set
- 1.16. web-sys: WebAudio
- 1.17. web-sys: WebGL
- 1.18. web-sys: WebSockets
- 1.19. web-sys: WebRTC DataChannel
- 1.20. web-sys: requestAnimationFrame
- 1.21. web-sys: A Simple Paint Program
- 1.22. web-sys: Wasm in Web Worker
- 1.23. Parallel Raytracing
- 1.24. Wasm Audio Worklet
- 1.25. web-sys: A TODO MVC App
- 2. Reference
- 2.1. Deployment
- 2.2. JS snippets
- 2.3. Static JS Objects
- 2.4. Passing Rust Closures to JS
- 2.5. Receiving JS Closures in Rust
- 2.6. Promises and Futures
- 2.7. Iterating over JS Values
- 2.8. Arbitrary Data with Serde
- 2.9. Accessing Properties of Untyped JS Values
- 2.10. Working with Duck-Typed Interfaces
- 2.11. Command Line Interface
- 2.12. Optimizing for Size
- 2.13. Supported Rust Targets
- 2.14. Supported Browsers
- 2.15. Support for Weak References
- 2.16. Support for Reference Types
- 2.17. Supported Types
- 2.18. #[wasm_bindgen] Attributes
- 2.18.1. On JavaScript Imports
- 2.18.1.1. catch
- 2.18.1.2. constructor
- 2.18.1.3. extends
- 2.18.1.4. getter and setter
- 2.18.1.5. final
- 2.18.1.6. indexing_getter, indexing_setter, and indexing_deleter
- 2.18.1.7. js_class = "Blah"
- 2.18.1.8. js_name
- 2.18.1.9. js_namespace
- 2.18.1.10. method
- 2.18.1.11. module = "blah"
- 2.18.1.12. raw_module = "blah"
- 2.18.1.13. no_deref
- 2.18.1.14. static_method_of = Blah
- 2.18.1.15. structural
- 2.18.1.16. typescript_type
- 2.18.1.17. variadic
- 2.18.1.18. vendor_prefix
- 2.18.1.1. catch
- 2.18.2. On Rust Exports
- 2.18.2.1. constructor
- 2.18.2.2. js_name = Blah
- 2.18.2.3. js_class = Blah
- 2.18.2.4. readonly
- 2.18.2.5. skip
- 2.18.2.6. skip_jsdoc
- 2.18.2.7. start
- 2.18.2.8. main
- 2.18.2.9. typescript_custom_section
- 2.18.2.10. getter and setter
- 2.18.2.11. inspectable
- 2.18.2.12. skip_typescript
- 2.18.2.13. getter_with_clone
- 2.18.2.14. unchecked_return_type and unchecked_param_type
- 2.18.2.15. return_description and param_description
- 2.18.2.1. constructor
- 3. web-sys
- 4. Testing with wasm-bindgen-test
- 5. Contributing to wasm-bindgen
- 5.2. Internal Design
- 5.3. js-sys
- 5.4. web-sys
- 5.5. Publishing
- 5.6. Team
The `wasm-bindgen` Guide
View full source code or view the compiled example online
One of the features of #[wasm_bindgen]
is that you can pass closures defined in Rust off to JS. This can be a bit tricky at times, though, so the example here shows how to interact with some standard web APIs with closures.
# #![allow(unused_variables)]
#fn main() {
use js_sys::{Array, Date};
use wasm_bindgen::prelude::*;
use web_sys::{Document, Element, HtmlElement, Window};
#[wasm_bindgen(start)]
fn run() -> Result<(), JsValue> {
let window = web_sys::window().expect("should have a window in this context");
let document = window.document().expect("window should have a document");
// One of the first interesting things we can do with closures is simply
// access stack data in Rust!
let array = Array::new();
array.push(&"Hello".into());
array.push(&1.into());
let mut first_item = None;
array.for_each(&mut |obj, idx, _arr| match idx {
0 => {
assert_eq!(obj, "Hello");
first_item = obj.as_string();
}
1 => assert_eq!(obj, 1),
_ => panic!("unknown index: {}", idx),
});
assert_eq!(first_item, Some("Hello".to_string()));
// Below are some more advanced usages of the `Closure` type for closures
// that need to live beyond our function call.
setup_clock(&window, &document)?;
setup_clicker(&document);
// And now that our demo is ready to go let's switch things up so
// everything is displayed and our loading prompt is hidden.
document
.get_element_by_id("loading")
.expect("should have #loading on the page")
.dyn_ref::<HtmlElement>()
.expect("#loading should be an `HtmlElement`")
.style()
.set_property("display", "none")?;
document
.get_element_by_id("script")
.expect("should have #script on the page")
.dyn_ref::<HtmlElement>()
.expect("#script should be an `HtmlElement`")
.style()
.set_property("display", "block")?;
Ok(())
}
// Set up a clock on our page and update it each second to ensure it's got
// an accurate date.
//
// Note the usage of `Closure` here because the closure is "long lived",
// basically meaning it has to persist beyond the call to this one function.
// Also of note here is the `.as_ref().unchecked_ref()` chain, which is how
// you can extract `&Function`, what `web-sys` expects, from a `Closure`
// which only hands you `&JsValue` via `AsRef`.
fn setup_clock(window: &Window, document: &Document) -> Result<(), JsValue> {
let current_time = document
.get_element_by_id("current-time")
.expect("should have #current-time on the page");
update_time(¤t_time);
let a = Closure::<dyn Fn()>::new(move || update_time(¤t_time));
window
.set_interval_with_callback_and_timeout_and_arguments_0(a.as_ref().unchecked_ref(), 1000)?;
fn update_time(current_time: &Element) {
current_time.set_inner_html(&String::from(
Date::new_0().to_locale_string("en-GB", &JsValue::undefined()),
));
}
// The instance of `Closure` that we created will invalidate its
// corresponding JS callback whenever it is dropped, so if we were to
// normally return from `setup_clock` then our registered closure will
// raise an exception when invoked.
//
// Normally we'd store the handle to later get dropped at an appropriate
// time but for now we want it to be a global handler so we use the
// `forget` method to drop it without invalidating the closure. Note that
// this is leaking memory in Rust, so this should be done judiciously!
a.forget();
Ok(())
}
// We also want to count the number of times that our green square has been
// clicked. Our callback will update the `#num-clicks` div.
//
// This is pretty similar above, but showing how closures can also implement
// `FnMut()`.
fn setup_clicker(document: &Document) {
let num_clicks = document
.get_element_by_id("num-clicks")
.expect("should have #num-clicks on the page");
let mut clicks = 0;
let a = Closure::<dyn FnMut()>::new(move || {
clicks += 1;
num_clicks.set_inner_html(&clicks.to_string());
});
document
.get_element_by_id("green-square")
.expect("should have #green-square on the page")
.dyn_ref::<HtmlElement>()
.expect("#green-square be an `HtmlElement`")
.set_onclick(Some(a.as_ref().unchecked_ref()));
// See comments in `setup_clock` above for why we use `a.forget()`.
a.forget();
}
#}