wasm_bindgen/
closure.rs

1//! Support for long-lived closures in `wasm-bindgen`
2//!
3//! This module defines the `Closure` type which is used to pass "owned
4//! closures" from Rust to JS. Some more details can be found on the `Closure`
5//! type itself.
6
7#![allow(clippy::fn_to_numeric_cast)]
8
9use alloc::boxed::Box;
10use alloc::string::String;
11use core::fmt;
12use core::mem::{self, ManuallyDrop};
13
14use crate::convert::*;
15use crate::describe::*;
16use crate::JsValue;
17
18/// A handle to both a closure in Rust as well as JS closure which will invoke
19/// the Rust closure.
20///
21/// A `Closure` is the primary way that a `'static` lifetime closure is
22/// transferred from Rust to JS. `Closure` currently requires that the closures
23/// it's created with have the `'static` lifetime in Rust for soundness reasons.
24///
25/// This type is a "handle" in the sense that whenever it is dropped it will
26/// invalidate the JS closure that it refers to. Any usage of the closure in JS
27/// after the `Closure` has been dropped will raise an exception. It's then up
28/// to you to arrange for `Closure` to be properly deallocate at an appropriate
29/// location in your program.
30///
31/// The type parameter on `Closure` is the type of closure that this represents.
32/// Currently this can only be the `Fn` and `FnMut` traits with up to 7
33/// arguments (and an optional return value).
34///
35/// # Examples
36///
37/// Here are a number of examples of using `Closure`.
38///
39/// ## Using the `setInterval` API
40///
41/// Sample usage of `Closure` to invoke the `setInterval` API.
42///
43/// ```rust,no_run
44/// use wasm_bindgen::prelude::*;
45///
46/// #[wasm_bindgen]
47/// extern "C" {
48///     fn setInterval(closure: &Closure<dyn FnMut()>, time: u32) -> i32;
49///     fn clearInterval(id: i32);
50///
51///     #[wasm_bindgen(js_namespace = console)]
52///     fn log(s: &str);
53/// }
54///
55/// #[wasm_bindgen]
56/// pub struct IntervalHandle {
57///     interval_id: i32,
58///     _closure: Closure<dyn FnMut()>,
59/// }
60///
61/// impl Drop for IntervalHandle {
62///     fn drop(&mut self) {
63///         clearInterval(self.interval_id);
64///     }
65/// }
66///
67/// #[wasm_bindgen]
68/// pub fn run() -> IntervalHandle {
69///     // First up we use `Closure::new` to wrap up a Rust closure and create
70///     // a JS closure.
71///     let cb = Closure::new(|| {
72///         log("interval elapsed!");
73///     });
74///
75///     // Next we pass this via reference to the `setInterval` function, and
76///     // `setInterval` gets a handle to the corresponding JS closure.
77///     let interval_id = setInterval(&cb, 1_000);
78///
79///     // If we were to drop `cb` here it would cause an exception to be raised
80///     // whenever the interval elapses. Instead we *return* our handle back to JS
81///     // so JS can decide when to cancel the interval and deallocate the closure.
82///     IntervalHandle {
83///         interval_id,
84///         _closure: cb,
85///     }
86/// }
87/// ```
88///
89/// ## Casting a `Closure` to a `js_sys::Function`
90///
91/// This is the same `setInterval` example as above, except it is using
92/// `web_sys` (which uses `js_sys::Function` for callbacks) instead of manually
93/// writing bindings to `setInterval` and other Web APIs.
94///
95/// ```rust,ignore
96/// use wasm_bindgen::JsCast;
97///
98/// #[wasm_bindgen]
99/// pub struct IntervalHandle {
100///     interval_id: i32,
101///     _closure: Closure<dyn FnMut()>,
102/// }
103///
104/// impl Drop for IntervalHandle {
105///     fn drop(&mut self) {
106///         let window = web_sys::window().unwrap();
107///         window.clear_interval_with_handle(self.interval_id);
108///     }
109/// }
110///
111/// #[wasm_bindgen]
112/// pub fn run() -> Result<IntervalHandle, JsValue> {
113///     let cb = Closure::new(|| {
114///         web_sys::console::log_1(&"interval elapsed!".into());
115///     });
116///
117///     let window = web_sys::window().unwrap();
118///     let interval_id = window.set_interval_with_callback_and_timeout_and_arguments_0(
119///         // Note this method call, which uses `as_ref()` to get a `JsValue`
120///         // from our `Closure` which is then converted to a `&Function`
121///         // using the `JsCast::unchecked_ref` function.
122///         cb.as_ref().unchecked_ref(),
123///         1_000,
124///     )?;
125///
126///     // Same as above.
127///     Ok(IntervalHandle {
128///         interval_id,
129///         _closure: cb,
130///     })
131/// }
132/// ```
133///
134/// ## Using `FnOnce` and `Closure::once` with `requestAnimationFrame`
135///
136/// Because `requestAnimationFrame` only calls its callback once, we can use
137/// `FnOnce` and `Closure::once` with it.
138///
139/// ```rust,no_run
140/// use wasm_bindgen::prelude::*;
141///
142/// #[wasm_bindgen]
143/// extern "C" {
144///     fn requestAnimationFrame(closure: &Closure<dyn FnMut()>) -> u32;
145///     fn cancelAnimationFrame(id: u32);
146///
147///     #[wasm_bindgen(js_namespace = console)]
148///     fn log(s: &str);
149/// }
150///
151/// #[wasm_bindgen]
152/// pub struct AnimationFrameHandle {
153///     animation_id: u32,
154///     _closure: Closure<dyn FnMut()>,
155/// }
156///
157/// impl Drop for AnimationFrameHandle {
158///     fn drop(&mut self) {
159///         cancelAnimationFrame(self.animation_id);
160///     }
161/// }
162///
163/// // A type that will log a message when it is dropped.
164/// struct LogOnDrop(&'static str);
165/// impl Drop for LogOnDrop {
166///     fn drop(&mut self) {
167///         log(self.0);
168///     }
169/// }
170///
171/// #[wasm_bindgen]
172/// pub fn run() -> AnimationFrameHandle {
173///     // We are using `Closure::once` which takes a `FnOnce`, so the function
174///     // can drop and/or move things that it closes over.
175///     let fired = LogOnDrop("animation frame fired or canceled");
176///     let cb = Closure::once(move || drop(fired));
177///
178///     // Schedule the animation frame!
179///     let animation_id = requestAnimationFrame(&cb);
180///
181///     // Again, return a handle to JS, so that the closure is not dropped
182///     // immediately and JS can decide whether to cancel the animation frame.
183///     AnimationFrameHandle {
184///         animation_id,
185///         _closure: cb,
186///     }
187/// }
188/// ```
189///
190/// ## Converting `FnOnce`s directly into JavaScript Functions with `Closure::once_into_js`
191///
192/// If we don't want to allow a `FnOnce` to be eagerly dropped (maybe because we
193/// just want it to drop after it is called and don't care about cancellation)
194/// then we can use the `Closure::once_into_js` function.
195///
196/// This is the same `requestAnimationFrame` example as above, but without
197/// supporting early cancellation.
198///
199/// ```
200/// use wasm_bindgen::prelude::*;
201///
202/// #[wasm_bindgen]
203/// extern "C" {
204///     // We modify the binding to take an untyped `JsValue` since that is what
205///     // is returned by `Closure::once_into_js`.
206///     //
207///     // If we were using the `web_sys` binding for `requestAnimationFrame`,
208///     // then the call sites would cast the `JsValue` into a `&js_sys::Function`
209///     // using `f.unchecked_ref::<js_sys::Function>()`. See the `web_sys`
210///     // example above for details.
211///     fn requestAnimationFrame(callback: JsValue);
212///
213///     #[wasm_bindgen(js_namespace = console)]
214///     fn log(s: &str);
215/// }
216///
217/// // A type that will log a message when it is dropped.
218/// struct LogOnDrop(&'static str);
219/// impl Drop for LogOnDrop {
220///     fn drop(&mut self) {
221///         log(self.0);
222///     }
223/// }
224///
225/// #[wasm_bindgen]
226/// pub fn run() {
227///     // We are using `Closure::once_into_js` which takes a `FnOnce` and
228///     // converts it into a JavaScript function, which is returned as a
229///     // `JsValue`.
230///     let fired = LogOnDrop("animation frame fired");
231///     let cb = Closure::once_into_js(move || drop(fired));
232///
233///     // Schedule the animation frame!
234///     requestAnimationFrame(cb);
235///
236///     // No need to worry about whether or not we drop a `Closure`
237///     // here or return some sort of handle to JS!
238/// }
239/// ```
240pub struct Closure<T: ?Sized> {
241    js: ManuallyDrop<JsValue>,
242    data: ManuallyDrop<Box<T>>,
243}
244
245union FatPtr<T: ?Sized> {
246    ptr: *mut T,
247    fields: (usize, usize),
248}
249
250impl<T> Closure<T>
251where
252    T: ?Sized + WasmClosure,
253{
254    /// Creates a new instance of `Closure` from the provided Rust function.
255    ///
256    /// Note that the closure provided here, `F`, has a few requirements
257    /// associated with it:
258    ///
259    /// * It must implement `Fn` or `FnMut` (for `FnOnce` functions see
260    ///   `Closure::once` and `Closure::once_into_js`).
261    ///
262    /// * It must be `'static`, aka no stack references (use the `move`
263    ///   keyword).
264    ///
265    /// * It can have at most 7 arguments.
266    ///
267    /// * Its arguments and return values are all types that can be shared with
268    ///   JS (i.e. have `#[wasm_bindgen]` annotations or are simple numbers,
269    ///   etc.)
270    pub fn new<F>(t: F) -> Closure<T>
271    where
272        F: IntoWasmClosure<T> + 'static,
273    {
274        Closure::wrap(Box::new(t).unsize())
275    }
276
277    /// A more direct version of `Closure::new` which creates a `Closure` from
278    /// a `Box<dyn Fn>`/`Box<dyn FnMut>`, which is how it's kept internally.
279    pub fn wrap(mut data: Box<T>) -> Closure<T> {
280        assert_eq!(mem::size_of::<*const T>(), mem::size_of::<FatPtr<T>>());
281        let (a, b) = unsafe {
282            FatPtr {
283                ptr: &mut *data as *mut T,
284            }
285            .fields
286        };
287
288        // Here we need to create a `JsValue` with the data and `T::invoke()`
289        // function pointer. To do that we... take a few unconventional turns.
290        // In essence what happens here is this:
291        //
292        // 1. First up, below we call a function, `breaks_if_inlined`. This
293        //    function, as the name implies, does not work if it's inlined.
294        //    More on that in a moment.
295        // 2. This function internally calls a special import recognized by the
296        //    `wasm-bindgen` CLI tool, `__wbindgen_describe_closure`. This
297        //    imported symbol is similar to `__wbindgen_describe` in that it's
298        //    not intended to show up in the final binary but it's an
299        //    intermediate state for a `wasm-bindgen` binary.
300        // 3. The `__wbindgen_describe_closure` import is namely passed a
301        //    descriptor function, monomorphized for each invocation.
302        //
303        // Most of this doesn't actually make sense to happen at runtime! The
304        // real magic happens when `wasm-bindgen` comes along and updates our
305        // generated code. When `wasm-bindgen` runs it performs a few tasks:
306        //
307        // * First, it finds all functions that call
308        //   `__wbindgen_describe_closure`. These are all `breaks_if_inlined`
309        //   defined below as the symbol isn't called anywhere else.
310        // * Next, `wasm-bindgen` executes the `breaks_if_inlined`
311        //   monomorphized functions, passing it dummy arguments. This will
312        //   execute the function just enough to invoke the special import,
313        //   namely telling us about the function pointer that is the describe
314        //   shim.
315        // * This knowledge is then used to actually find the descriptor in the
316        //   function table which is then executed to figure out the signature
317        //   of the closure.
318        // * Finally, and probably most heinously, the call to
319        //   `breaks_if_inlined` is rewritten to call an otherwise globally
320        //   imported function. This globally imported function will generate
321        //   the `JsValue` for this closure specialized for the signature in
322        //   question.
323        //
324        // Later on `wasm-gc` will clean up all the dead code and ensure that
325        // we don't actually call `__wbindgen_describe_closure` at runtime. This
326        // means we will end up not actually calling `breaks_if_inlined` in the
327        // final binary, all calls to that function should be pruned.
328        //
329        // See crates/cli-support/src/js/closures.rs for a more information
330        // about what's going on here.
331
332        #[cfg_attr(wasm_bindgen_unstable_test_coverage, coverage(off))]
333        extern "C" fn describe<T: WasmClosure + ?Sized>() {
334            inform(CLOSURE);
335
336            unsafe extern "C" fn destroy<T: ?Sized>(a: usize, b: usize) {
337                // This can be called by the JS glue in erroneous situations
338                // such as when the closure has already been destroyed. If
339                // that's the case let's not make things worse by
340                // segfaulting and/or asserting, so just ignore null
341                // pointers.
342                if a == 0 {
343                    return;
344                }
345                drop(Box::from_raw(FatPtr::<T> { fields: (a, b) }.ptr));
346            }
347            inform(destroy::<T> as usize as u32);
348
349            inform(T::IS_MUT as u32);
350            T::describe();
351        }
352
353        #[inline(never)]
354        #[cfg_attr(wasm_bindgen_unstable_test_coverage, coverage(off))]
355        unsafe fn breaks_if_inlined<T: WasmClosure + ?Sized>(a: usize, b: usize) -> u32 {
356            super::__wbindgen_describe_closure(a as u32, b as u32, describe::<T> as usize as u32)
357        }
358
359        let idx = unsafe { breaks_if_inlined::<T>(a, b) };
360
361        Closure {
362            js: ManuallyDrop::new(JsValue::_new(idx)),
363            data: ManuallyDrop::new(data),
364        }
365    }
366
367    /// Release memory management of this closure from Rust to the JS GC.
368    ///
369    /// When a `Closure` is dropped it will release the Rust memory and
370    /// invalidate the associated JS closure, but this isn't always desired.
371    /// Some callbacks are alive for the entire duration of the program or for a
372    /// lifetime dynamically managed by the JS GC. This function can be used
373    /// to drop this `Closure` while keeping the associated JS function still
374    /// valid.
375    ///
376    /// If the platform supports weak references, the Rust memory will be
377    /// reclaimed when the JS closure is GC'd. If weak references is not
378    /// supported, this can be dangerous if this function is called many times
379    /// in an application because the memory leak will overwhelm the page
380    /// quickly and crash the wasm.
381    pub fn into_js_value(self) -> JsValue {
382        let idx = self.js.idx;
383        mem::forget(self);
384        JsValue::_new(idx)
385    }
386
387    /// Same as `into_js_value`, but doesn't return a value.
388    pub fn forget(self) {
389        drop(self.into_js_value());
390    }
391
392    /// Create a `Closure` from a function that can only be called once.
393    ///
394    /// Since we have no way of enforcing that JS cannot attempt to call this
395    /// `FnOne(A...) -> R` more than once, this produces a `Closure<dyn FnMut(A...)
396    /// -> R>` that will dynamically throw a JavaScript error if called more
397    /// than once.
398    ///
399    /// # Example
400    ///
401    /// ```rust,no_run
402    /// use wasm_bindgen::prelude::*;
403    ///
404    /// // Create an non-`Copy`, owned `String`.
405    /// let mut s = String::from("Hello");
406    ///
407    /// // Close over `s`. Since `f` returns `s`, it is `FnOnce` and can only be
408    /// // called once. If it was called a second time, it wouldn't have any `s`
409    /// // to work with anymore!
410    /// let f = move || {
411    ///     s += ", World!";
412    ///     s
413    /// };
414    ///
415    /// // Create a `Closure` from `f`. Note that the `Closure`'s type parameter
416    /// // is `FnMut`, even though `f` is `FnOnce`.
417    /// let closure: Closure<dyn FnMut() -> String> = Closure::once(f);
418    /// ```
419    ///
420    /// Note: the `A` and `R` type parameters are here just for backward compat
421    /// and will be removed in the future.
422    pub fn once<F, A, R>(fn_once: F) -> Self
423    where
424        F: WasmClosureFnOnce<T, A, R>,
425    {
426        Closure::wrap(fn_once.into_fn_mut())
427    }
428
429    /// Convert a `FnOnce(A...) -> R` into a JavaScript `Function` object.
430    ///
431    /// If the JavaScript function is invoked more than once, it will throw an
432    /// exception.
433    ///
434    /// Unlike `Closure::once`, this does *not* return a `Closure` that can be
435    /// dropped before the function is invoked to deallocate the closure. The
436    /// only way the `FnOnce` is deallocated is by calling the JavaScript
437    /// function. If the JavaScript function is never called then the `FnOnce`
438    /// and everything it closes over will leak.
439    ///
440    /// ```rust,ignore
441    /// use wasm_bindgen::{prelude::*, JsCast};
442    ///
443    /// let f = Closure::once_into_js(move || {
444    ///     // ...
445    /// });
446    ///
447    /// assert!(f.is_instance_of::<js_sys::Function>());
448    /// ```
449    ///
450    /// Note: the `A` and `R` type parameters are here just for backward compat
451    /// and will be removed in the future.
452    pub fn once_into_js<F, A, R>(fn_once: F) -> JsValue
453    where
454        F: WasmClosureFnOnce<T, A, R>,
455    {
456        fn_once.into_js_function()
457    }
458}
459
460/// A trait for converting an `FnOnce(A...) -> R` into a `FnMut(A...) -> R` that
461/// will throw if ever called more than once.
462#[doc(hidden)]
463pub trait WasmClosureFnOnce<FnMut: ?Sized, A, R>: 'static {
464    fn into_fn_mut(self) -> Box<FnMut>;
465
466    fn into_js_function(self) -> JsValue;
467}
468
469impl<T: ?Sized> AsRef<JsValue> for Closure<T> {
470    fn as_ref(&self) -> &JsValue {
471        &self.js
472    }
473}
474
475impl<T> WasmDescribe for Closure<T>
476where
477    T: WasmClosure + ?Sized,
478{
479    #[cfg_attr(wasm_bindgen_unstable_test_coverage, coverage(off))]
480    fn describe() {
481        inform(EXTERNREF);
482    }
483}
484
485// `Closure` can only be passed by reference to imports.
486impl<T> IntoWasmAbi for &Closure<T>
487where
488    T: WasmClosure + ?Sized,
489{
490    type Abi = u32;
491
492    fn into_abi(self) -> u32 {
493        (&*self.js).into_abi()
494    }
495}
496
497impl<T> OptionIntoWasmAbi for &Closure<T>
498where
499    T: WasmClosure + ?Sized,
500{
501    fn none() -> Self::Abi {
502        0
503    }
504}
505
506fn _check() {
507    fn _assert<T: IntoWasmAbi>() {}
508    _assert::<&Closure<dyn Fn()>>();
509    _assert::<&Closure<dyn Fn(String)>>();
510    _assert::<&Closure<dyn Fn() -> String>>();
511    _assert::<&Closure<dyn FnMut()>>();
512    _assert::<&Closure<dyn FnMut(String)>>();
513    _assert::<&Closure<dyn FnMut() -> String>>();
514}
515
516impl<T> fmt::Debug for Closure<T>
517where
518    T: ?Sized,
519{
520    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
521        write!(f, "Closure {{ ... }}")
522    }
523}
524
525impl<T> Drop for Closure<T>
526where
527    T: ?Sized,
528{
529    fn drop(&mut self) {
530        unsafe {
531            // this will implicitly drop our strong reference in addition to
532            // invalidating all future invocations of the closure
533            if super::__wbindgen_cb_drop(self.js.idx) != 0 {
534                ManuallyDrop::drop(&mut self.data);
535            }
536        }
537    }
538}
539
540/// An internal trait for the `Closure` type.
541///
542/// This trait is not stable and it's not recommended to use this in bounds or
543/// implement yourself.
544#[doc(hidden)]
545pub unsafe trait WasmClosure: WasmDescribe {
546    const IS_MUT: bool;
547}
548
549/// An internal trait for the `Closure` type.
550///
551/// This trait is not stable and it's not recommended to use this in bounds or
552/// implement yourself.
553#[doc(hidden)]
554pub trait IntoWasmClosure<T: ?Sized> {
555    fn unsize(self: Box<Self>) -> Box<T>;
556}