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