web-sys: Closures - The wasm-bindgen Guide (original) (raw)

  1. Introduction
  2. 1. Examples
    1. 1.1. Hello, World!
    2. 1.2. Using console.log
    3. 1.3. Small Wasm files
    4. 1.4. Without a Bundler
    5. 1.5. Synchronous Instantiation
    6. 1.6. Importing functions from JS
    7. 1.7. Working with char
    8. 1.8. js-sys: WebAssembly in WebAssembly
    9. 1.9. web-sys: DOM hello world
    10. 1.10. web-sys: Closures
    11. 1.11. web-sys: performance.now
    12. 1.12. web-sys: using fetch
    13. 1.13. web-sys: Weather report
    14. 1.14. web-sys: canvas hello world
    15. 1.15. web-sys: canvas Julia set
    16. 1.16. web-sys: WebAudio
    17. 1.17. web-sys: WebGL
    18. 1.18. web-sys: WebSockets
    19. 1.19. web-sys: WebRTC DataChannel
    20. 1.20. web-sys: requestAnimationFrame
    21. 1.21. web-sys: A Simple Paint Program
    22. 1.22. web-sys: Wasm in Web Worker
    23. 1.23. Parallel Raytracing
    24. 1.24. Wasm Audio Worklet
    25. 1.25. web-sys: A TODO MVC App
  3. 2. Reference
    1. 2.1. Deployment
    2. 2.2. JS snippets
    3. 2.3. Static JS Objects
    4. 2.4. Passing Rust Closures to JS
    5. 2.5. Receiving JS Closures in Rust
    6. 2.6. Promises and Futures
    7. 2.7. Iterating over JS Values
    8. 2.8. Arbitrary Data with Serde
    9. 2.9. Accessing Properties of Untyped JS Values
    10. 2.10. Working with Duck-Typed Interfaces
    11. 2.11. Command Line Interface
    12. 2.12. Optimizing for Size
    13. 2.13. Supported Rust Targets
    14. 2.14. Supported Browsers
    15. 2.15. Support for Weak References
    16. 2.16. Support for Reference Types
    17. 2.17. Supported Types
      1. 2.17.1. Imported JavaScript Types
      2. 2.17.2. Exported Rust Types
      3. 2.17.3. JsValue
      4. 2.17.4. Box<[T]> and Vec
      5. 2.17.5. *const T and *mut T
      6. 2.17.6. NonNull
      7. 2.17.7. Numbers
      8. 2.17.8. bool
      9. 2.17.9. char
      10. 2.17.10. str
      11. 2.17.11. String
      12. 2.17.12. Number Slices
      13. 2.17.13. Boxed Number Slices
      14. 2.17.14. Result<T, E>
    18. 2.18. #[wasm_bindgen] Attributes
      1. 2.18.1. On JavaScript Imports
        1. 2.18.1.1. catch
          1. 2.18.1.2. constructor
          2. 2.18.1.3. extends
          3. 2.18.1.4. getter and setter
          4. 2.18.1.5. final
          5. 2.18.1.6. indexing_getter, indexing_setter, and indexing_deleter
          6. 2.18.1.7. js_class = "Blah"
          7. 2.18.1.8. js_name
          8. 2.18.1.9. js_namespace
          9. 2.18.1.10. method
          10. 2.18.1.11. module = "blah"
          11. 2.18.1.12. raw_module = "blah"
          12. 2.18.1.13. no_deref
          13. 2.18.1.14. static_method_of = Blah
          14. 2.18.1.15. structural
          15. 2.18.1.16. typescript_type
          16. 2.18.1.17. variadic
          17. 2.18.1.18. vendor_prefix
      2. 2.18.2. On Rust Exports
        1. 2.18.2.1. constructor
          1. 2.18.2.2. js_name = Blah
          2. 2.18.2.3. js_class = Blah
          3. 2.18.2.4. readonly
          4. 2.18.2.5. skip
          5. 2.18.2.6. skip_jsdoc
          6. 2.18.2.7. start
          7. 2.18.2.8. main
          8. 2.18.2.9. typescript_custom_section
          9. 2.18.2.10. getter and setter
          10. 2.18.2.11. inspectable
          11. 2.18.2.12. skip_typescript
          12. 2.18.2.13. getter_with_clone
          13. 2.18.2.14. unchecked_return_type and unchecked_param_type
          14. 2.18.2.15. return_description and param_description
  4. 3. web-sys
    1. 3.1. Using web-sys
    2. 3.2. Cargo Features
    3. 3.3. Function Overloads
    4. 3.4. Type Translations
    5. 3.5. Inheritance
    6. 3.6. Unstable APIs
  5. 4. Testing with wasm-bindgen-test
    1. 4.1. Usage
    2. 4.2. Writing Asynchronous Tests
    3. 4.3. Testing in Headless Browsers
    4. 4.4. Continuous Integration
    5. 4.5. Coverage (Experimental)
  6. 5. Contributing to wasm-bindgen
    1. 5.1. Testing
  7. 5.2. Internal Design
    1. 5.2.1. JS Objects in Rust
      1. 5.2.2. Exporting a function to JS
      2. 5.2.3. Exporting a struct to JS
      3. 5.2.4. Importing a function from JS
      4. 5.2.5. Importing a class from JS
      5. 5.2.6. Rust Type conversions
      6. 5.2.7. Types in wasm-bindgen
  8. 5.3. js-sys
    1. 5.3.1. Testing
      1. 5.3.2. Adding More APIs
  9. 5.4. web-sys
    1. 5.4.1. Overview
      1. 5.4.2. Testing
      2. 5.4.3. Logging
      3. 5.4.4. Supporting More Web APIs
  10. 5.5. Publishing
  11. 5.6. Team

The `wasm-bindgen` Guide

web-sys: Closures

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.

src/lib.rs


# #![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(&current_time);
    let a = Closure::<dyn Fn()>::new(move || update_time(&current_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();
}

#}