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}