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: OwnedClosure<T>,
243}
244
245impl<T> Closure<T>
246where
247 T: ?Sized + WasmClosure,
248{
249 /// Creates a new instance of `Closure` from the provided Rust function.
250 ///
251 /// Note that the closure provided here, `F`, has a few requirements
252 /// associated with it:
253 ///
254 /// * It must implement `Fn` or `FnMut` (for `FnOnce` functions see
255 /// `Closure::once` and `Closure::once_into_js`).
256 ///
257 /// * It must be `'static`, aka no stack references (use the `move`
258 /// keyword).
259 ///
260 /// * It can have at most 7 arguments.
261 ///
262 /// * Its arguments and return values are all types that can be shared with
263 /// JS (i.e. have `#[wasm_bindgen]` annotations or are simple numbers,
264 /// etc.)
265 pub fn new<F>(t: F) -> Closure<T>
266 where
267 F: IntoWasmClosure<T> + 'static,
268 {
269 Closure::wrap(Box::new(t).unsize())
270 }
271
272 /// A more direct version of `Closure::new` which creates a `Closure` from
273 /// a `Box<dyn Fn>`/`Box<dyn FnMut>`, which is how it's kept internally.
274 pub fn wrap(data: Box<T>) -> Closure<T> {
275 let data = OwnedClosure {
276 inner: ManuallyDrop::new(data),
277 };
278 Self {
279 js: ManuallyDrop::new(crate::__rt::wbg_cast(&data)),
280 data,
281 }
282 }
283
284 /// Release memory management of this closure from Rust to the JS GC.
285 ///
286 /// When a `Closure` is dropped it will release the Rust memory and
287 /// invalidate the associated JS closure, but this isn't always desired.
288 /// Some callbacks are alive for the entire duration of the program or for a
289 /// lifetime dynamically managed by the JS GC. This function can be used
290 /// to drop this `Closure` while keeping the associated JS function still
291 /// valid.
292 ///
293 /// If the platform supports weak references, the Rust memory will be
294 /// reclaimed when the JS closure is GC'd. If weak references is not
295 /// supported, this can be dangerous if this function is called many times
296 /// in an application because the memory leak will overwhelm the page
297 /// quickly and crash the wasm.
298 pub fn into_js_value(self) -> JsValue {
299 let idx = self.js.idx;
300 mem::forget(self);
301 JsValue::_new(idx)
302 }
303
304 /// Same as `into_js_value`, but doesn't return a value.
305 pub fn forget(self) {
306 drop(self.into_js_value());
307 }
308
309 /// Create a `Closure` from a function that can only be called once.
310 ///
311 /// Since we have no way of enforcing that JS cannot attempt to call this
312 /// `FnOne(A...) -> R` more than once, this produces a `Closure<dyn FnMut(A...)
313 /// -> R>` that will dynamically throw a JavaScript error if called more
314 /// than once.
315 ///
316 /// # Example
317 ///
318 /// ```rust,no_run
319 /// use wasm_bindgen::prelude::*;
320 ///
321 /// // Create an non-`Copy`, owned `String`.
322 /// let mut s = String::from("Hello");
323 ///
324 /// // Close over `s`. Since `f` returns `s`, it is `FnOnce` and can only be
325 /// // called once. If it was called a second time, it wouldn't have any `s`
326 /// // to work with anymore!
327 /// let f = move || {
328 /// s += ", World!";
329 /// s
330 /// };
331 ///
332 /// // Create a `Closure` from `f`. Note that the `Closure`'s type parameter
333 /// // is `FnMut`, even though `f` is `FnOnce`.
334 /// let closure: Closure<dyn FnMut() -> String> = Closure::once(f);
335 /// ```
336 ///
337 /// Note: the `A` and `R` type parameters are here just for backward compat
338 /// and will be removed in the future.
339 pub fn once<F, A, R>(fn_once: F) -> Self
340 where
341 F: WasmClosureFnOnce<T, A, R>,
342 {
343 Closure::wrap(fn_once.into_fn_mut())
344 }
345
346 /// Convert a `FnOnce(A...) -> R` into a JavaScript `Function` object.
347 ///
348 /// If the JavaScript function is invoked more than once, it will throw an
349 /// exception.
350 ///
351 /// Unlike `Closure::once`, this does *not* return a `Closure` that can be
352 /// dropped before the function is invoked to deallocate the closure. The
353 /// only way the `FnOnce` is deallocated is by calling the JavaScript
354 /// function. If the JavaScript function is never called then the `FnOnce`
355 /// and everything it closes over will leak.
356 ///
357 /// ```rust,ignore
358 /// use wasm_bindgen::{prelude::*, JsCast};
359 ///
360 /// let f = Closure::once_into_js(move || {
361 /// // ...
362 /// });
363 ///
364 /// assert!(f.is_instance_of::<js_sys::Function>());
365 /// ```
366 ///
367 /// Note: the `A` and `R` type parameters are here just for backward compat
368 /// and will be removed in the future.
369 pub fn once_into_js<F, A, R>(fn_once: F) -> JsValue
370 where
371 F: WasmClosureFnOnce<T, A, R>,
372 {
373 fn_once.into_js_function()
374 }
375}
376
377/// A trait for converting an `FnOnce(A...) -> R` into a `FnMut(A...) -> R` that
378/// will throw if ever called more than once.
379#[doc(hidden)]
380pub trait WasmClosureFnOnce<FnMut: ?Sized, A, R>: 'static {
381 fn into_fn_mut(self) -> Box<FnMut>;
382
383 fn into_js_function(self) -> JsValue;
384}
385
386impl<T: ?Sized> AsRef<JsValue> for Closure<T> {
387 fn as_ref(&self) -> &JsValue {
388 &self.js
389 }
390}
391
392/// Internal representation of the actual owned closure which we send to the JS
393/// in the constructor to convert it into a JavaScript value.
394#[repr(transparent)]
395struct OwnedClosure<T: ?Sized> {
396 inner: ManuallyDrop<Box<T>>,
397}
398
399impl<T> WasmDescribe for &OwnedClosure<T>
400where
401 T: WasmClosure + ?Sized,
402{
403 #[cfg_attr(wasm_bindgen_unstable_test_coverage, coverage(off))]
404 fn describe() {
405 inform(CLOSURE);
406
407 unsafe extern "C" fn destroy<T: ?Sized>(a: usize, b: usize) {
408 // This can be called by the JS glue in erroneous situations
409 // such as when the closure has already been destroyed. If
410 // that's the case let's not make things worse by
411 // segfaulting and/or asserting, so just ignore null
412 // pointers.
413 if a == 0 {
414 return;
415 }
416 drop(mem::transmute_copy::<_, Box<T>>(&(a, b)));
417 }
418 inform(destroy::<T> as usize as u32);
419
420 inform(T::IS_MUT as u32);
421 T::describe();
422 }
423}
424
425impl<T> IntoWasmAbi for &OwnedClosure<T>
426where
427 T: WasmClosure + ?Sized,
428{
429 type Abi = WasmSlice;
430
431 fn into_abi(self) -> WasmSlice {
432 let (a, b): (usize, usize) = unsafe { mem::transmute_copy(self) };
433 WasmSlice {
434 ptr: a as u32,
435 len: b as u32,
436 }
437 }
438}
439
440impl<T> WasmDescribe for Closure<T>
441where
442 T: WasmClosure + ?Sized,
443{
444 #[cfg_attr(wasm_bindgen_unstable_test_coverage, coverage(off))]
445 fn describe() {
446 inform(EXTERNREF);
447 }
448}
449
450// `Closure` can only be passed by reference to imports.
451impl<T> IntoWasmAbi for &Closure<T>
452where
453 T: WasmClosure + ?Sized,
454{
455 type Abi = u32;
456
457 fn into_abi(self) -> u32 {
458 (&*self.js).into_abi()
459 }
460}
461
462impl<T> OptionIntoWasmAbi for &Closure<T>
463where
464 T: WasmClosure + ?Sized,
465{
466 fn none() -> Self::Abi {
467 0
468 }
469}
470
471fn _check() {
472 fn _assert<T: IntoWasmAbi>() {}
473 _assert::<&Closure<dyn Fn()>>();
474 _assert::<&Closure<dyn Fn(String)>>();
475 _assert::<&Closure<dyn Fn() -> String>>();
476 _assert::<&Closure<dyn FnMut()>>();
477 _assert::<&Closure<dyn FnMut(String)>>();
478 _assert::<&Closure<dyn FnMut() -> String>>();
479}
480
481impl<T> fmt::Debug for Closure<T>
482where
483 T: ?Sized,
484{
485 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
486 write!(f, "Closure {{ ... }}")
487 }
488}
489
490impl<T> Drop for Closure<T>
491where
492 T: ?Sized,
493{
494 fn drop(&mut self) {
495 // this will implicitly drop our strong reference in addition to
496 // invalidating all future invocations of the closure
497 if super::__wbindgen_cb_drop(&self.js) {
498 unsafe {
499 ManuallyDrop::drop(&mut self.data.inner);
500 }
501 }
502 }
503}
504
505/// An internal trait for the `Closure` type.
506///
507/// This trait is not stable and it's not recommended to use this in bounds or
508/// implement yourself.
509#[doc(hidden)]
510pub unsafe trait WasmClosure: WasmDescribe {
511 const IS_MUT: bool;
512}
513
514/// An internal trait for the `Closure` type.
515///
516/// This trait is not stable and it's not recommended to use this in bounds or
517/// implement yourself.
518#[doc(hidden)]
519pub trait IntoWasmClosure<T: ?Sized> {
520 fn unsize(self: Box<Self>) -> Box<T>;
521}