mirror of
https://github.com/fluencelabs/wasm-bindgen
synced 2025-03-28 07:51:07 +00:00
Merge pull request #1281 from fitzgen/new-fn-once
Add support for `FnOnce` to `Closure<T>`
This commit is contained in:
commit
ba84db5007
@ -21,7 +21,6 @@ features = ['serde-serialize']
|
|||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
test = false
|
test = false
|
||||||
doctest = false
|
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["spans", "std"]
|
default = ["spans", "std"]
|
||||||
|
@ -3,4 +3,4 @@ steps:
|
|||||||
displayName: "Add WebAssembly target via rustup"
|
displayName: "Add WebAssembly target via rustup"
|
||||||
- task: NodeTool@0
|
- task: NodeTool@0
|
||||||
inputs:
|
inputs:
|
||||||
versionSpec: '10.x'
|
versionSpec: '>=10.11'
|
||||||
|
395
src/closure.rs
395
src/closure.rs
@ -9,10 +9,11 @@ use std::marker::Unsize;
|
|||||||
use std::mem::{self, ManuallyDrop};
|
use std::mem::{self, ManuallyDrop};
|
||||||
use std::prelude::v1::*;
|
use std::prelude::v1::*;
|
||||||
|
|
||||||
use JsValue;
|
|
||||||
use convert::*;
|
use convert::*;
|
||||||
use describe::*;
|
use describe::*;
|
||||||
use throw_str;
|
use throw_str;
|
||||||
|
use JsValue;
|
||||||
|
use UnwrapThrowExt;
|
||||||
|
|
||||||
/// A handle to both a closure in Rust as well as JS closure which will invoke
|
/// A handle to both a closure in Rust as well as JS closure which will invoke
|
||||||
/// the Rust closure.
|
/// the Rust closure.
|
||||||
@ -33,69 +34,209 @@ use throw_str;
|
|||||||
/// trait must be numbers like `u32` for now, although this restriction may be
|
/// trait must be numbers like `u32` for now, although this restriction may be
|
||||||
/// lifted in the future!
|
/// lifted in the future!
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// Sample usage of `Closure` to invoke the `setTimeout` API.
|
/// Here are a number of examples of using `Closure`.
|
||||||
|
///
|
||||||
|
/// ## Using the `setInterval` API
|
||||||
|
///
|
||||||
|
/// Sample usage of `Closure` to invoke the `setInterval` API.
|
||||||
///
|
///
|
||||||
/// ```rust,no_run
|
/// ```rust,no_run
|
||||||
|
/// use wasm_bindgen::prelude::*;
|
||||||
|
///
|
||||||
/// #[wasm_bindgen]
|
/// #[wasm_bindgen]
|
||||||
/// extern "C" {
|
/// extern "C" {
|
||||||
/// fn setTimeout(closure: &Closure<FnMut()>, time: u32);
|
/// fn setInterval(closure: &Closure<FnMut()>, time: u32) -> i32;
|
||||||
|
/// fn clearInterval(id: i32);
|
||||||
///
|
///
|
||||||
/// #[wasm_bindgen(js_namespace = console)]
|
/// #[wasm_bindgen(js_namespace = console)]
|
||||||
/// fn log(s: &str);
|
/// fn log(s: &str);
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// #[wasm_bindgen]
|
/// #[wasm_bindgen]
|
||||||
/// pub struct ClosureHandle(Closure<FnMut()>);
|
/// pub struct IntervalHandle {
|
||||||
|
/// interval_id: i32,
|
||||||
|
/// _closure: Closure<FnMut()>,
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// impl Drop for IntervalHandle {
|
||||||
|
/// fn drop(&mut self) {
|
||||||
|
/// clearInterval(self.interval_id);
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
///
|
///
|
||||||
/// #[wasm_bindgen]
|
/// #[wasm_bindgen]
|
||||||
/// pub fn run() -> ClosureHandle {
|
/// pub fn run() -> IntervalHandle {
|
||||||
/// // First up we use `Closure::wrap` to wrap up a Rust closure and create
|
/// // First up we use `Closure::wrap` to wrap up a Rust closure and create
|
||||||
/// // a JS closure.
|
/// // a JS closure.
|
||||||
/// let cb = Closure::wrap(Box::new(move || {
|
/// let cb = Closure::wrap(Box::new(|| {
|
||||||
/// log("timeout elapsed!");
|
/// log("interval elapsed!");
|
||||||
/// }) as Box<FnMut()>);
|
/// }) as Box<FnMut()>);
|
||||||
///
|
///
|
||||||
/// // Next we pass this via reference to the `setTimeout` function, and
|
/// // Next we pass this via reference to the `setInterval` function, and
|
||||||
/// // `setTimeout` gets a handle to the corresponding JS closure.
|
/// // `setInterval` gets a handle to the corresponding JS closure.
|
||||||
/// setTimeout(&cb, 1_000);
|
/// let interval_id = setInterval(&cb, 1_000);
|
||||||
///
|
///
|
||||||
/// // If we were to drop `cb` here it would cause an exception to be raised
|
/// // If we were to drop `cb` here it would cause an exception to be raised
|
||||||
/// // when the timeout elapses. Instead we *return* our handle back to JS
|
/// // whenever the interval elapses. Instead we *return* our handle back to JS
|
||||||
/// // so JS can tell us later when it would like to deallocate this handle.
|
/// // so JS can decide when to cancel the interval and deallocate the closure.
|
||||||
/// ClosureHandle(cb)
|
/// IntervalHandle {
|
||||||
|
/// interval_id,
|
||||||
|
/// _closure: cb,
|
||||||
|
/// }
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// Sample usage of the same example as above except using `web_sys` instead
|
/// ## Casting a `Closure` to a `js_sys::Function`
|
||||||
///
|
///
|
||||||
/// ```rust,no_run
|
/// This is the same `setInterval` example as above, except it is using
|
||||||
/// extern crate wasm_bindgen;
|
/// `web_sys` (which uses `js_sys::Function` for callbacks) instead of manually
|
||||||
/// extern crate web_sys;
|
/// writing bindings to `setInterval` and other Web APIs.
|
||||||
///
|
///
|
||||||
|
/// ```rust,ignore
|
||||||
/// use wasm_bindgen::JsCast;
|
/// use wasm_bindgen::JsCast;
|
||||||
///
|
///
|
||||||
/// #[wasm_bindgen]
|
/// #[wasm_bindgen]
|
||||||
/// pub struct ClosureHandle(Closure<FnMut()>);
|
/// pub struct IntervalHandle {
|
||||||
|
/// interval_id: i32,
|
||||||
|
/// _closure: Closure<FnMut()>,
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// impl Drop for IntervalHandle {
|
||||||
|
/// fn drop(&mut self) {
|
||||||
|
/// let window = web_sys::window().unwrap();
|
||||||
|
/// window.clear_interval_with_handle(self.interval_id);
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
///
|
///
|
||||||
/// #[wasm_bindgen]
|
/// #[wasm_bindgen]
|
||||||
/// pub fn run() -> ClosureHandle {
|
/// pub fn run() -> Result<IntervalHandle, JsValue> {
|
||||||
/// let cb = Closure::wrap(Box::new(move || {
|
/// let cb = Closure::wrap(Box::new(|| {
|
||||||
/// web_sys::console::log_1(&"timeout elapsed!".into());
|
/// web_sys::console::log_1(&"inverval elapsed!".into());
|
||||||
/// }) as Box<FnMut()>);
|
/// }) as Box<FnMut()>);
|
||||||
///
|
///
|
||||||
/// let window = web_sys::window().unwrap();
|
/// let window = web_sys::window().unwrap();
|
||||||
/// window.set_timeout_with_callback_and_timeout_and_arguments_0(
|
/// let interval_id = window.set_interval_with_callback_and_timeout_and_arguments_0(
|
||||||
/// // Note this method call, which uses `as_ref()` to get a `JsValue`
|
/// // Note this method call, which uses `as_ref()` to get a `JsValue`
|
||||||
/// // from our `Closure` which is then converted to a `&Function`
|
/// // from our `Closure` which is then converted to a `&Function`
|
||||||
/// // using the `JsCast::unchecked_ref` function.
|
/// // using the `JsCast::unchecked_ref` function.
|
||||||
/// cb.as_ref().unchecked_ref(),
|
/// cb.as_ref().upnchecked_ref(),
|
||||||
/// 1_000,
|
/// 1_000,
|
||||||
/// );
|
/// )?;
|
||||||
///
|
///
|
||||||
/// // same as above
|
/// // Same as above.
|
||||||
/// ClosureHandle(cb)
|
/// Ok(IntervalHandle {
|
||||||
|
/// interval_id,
|
||||||
|
/// _closure: cb,
|
||||||
|
/// })
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ## Using `FnOnce` and `Closure::once` with `requestAnimationFrame`
|
||||||
|
///
|
||||||
|
/// Because `requestAnimationFrame` only calls its callback once, we can use
|
||||||
|
/// `FnOnce` and `Closure::once` with it.
|
||||||
|
///
|
||||||
|
/// ```rust,no_run
|
||||||
|
/// use wasm_bindgen::prelude::*;
|
||||||
|
///
|
||||||
|
/// #[wasm_bindgen]
|
||||||
|
/// extern "C" {
|
||||||
|
/// fn requestAnimationFrame(closure: &Closure<FnMut()>) -> u32;
|
||||||
|
/// fn cancelAnimationFrame(id: u32);
|
||||||
|
///
|
||||||
|
/// #[wasm_bindgen(js_namespace = console)]
|
||||||
|
/// fn log(s: &str);
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// #[wasm_bindgen]
|
||||||
|
/// pub struct AnimationFrameHandle {
|
||||||
|
/// animation_id: u32,
|
||||||
|
/// _closure: Closure<FnMut()>,
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// impl Drop for AnimationFrameHandle {
|
||||||
|
/// fn drop(&mut self) {
|
||||||
|
/// cancelAnimationFrame(self.animation_id);
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// // A type that will log a message when it is dropped.
|
||||||
|
/// struct LogOnDrop(&'static str);
|
||||||
|
/// impl Drop for LogOnDrop {
|
||||||
|
/// fn drop(&mut self) {
|
||||||
|
/// log(self.0);
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// #[wasm_bindgen]
|
||||||
|
/// pub fn run() -> AnimationFrameHandle {
|
||||||
|
/// // We are using `Closure::once` which takes a `FnOnce`, so the function
|
||||||
|
/// // can drop and/or move things that it closes over.
|
||||||
|
/// let fired = LogOnDrop("animation frame fired or canceled");
|
||||||
|
/// let cb = Closure::once(move || drop(fired));
|
||||||
|
///
|
||||||
|
/// // Schedule the animation frame!
|
||||||
|
/// let animation_id = requestAnimationFrame(&cb);
|
||||||
|
///
|
||||||
|
/// // Again, return a handle to JS, so that the closure is not dropped
|
||||||
|
/// // immediately and JS can decide whether to cancel the animation frame.
|
||||||
|
/// AnimationFrameHandle {
|
||||||
|
/// animation_id,
|
||||||
|
/// _closure: cb,
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ## Converting `FnOnce`s directly into JavaScript Functions with `Closure::once_into_js`
|
||||||
|
///
|
||||||
|
/// If we don't want to allow a `FnOnce` to be eagerly dropped (maybe because we
|
||||||
|
/// just want it to drop after it is called and don't care about cancellation)
|
||||||
|
/// then we can use the `Closure::once_into_js` function.
|
||||||
|
///
|
||||||
|
/// This is the same `requestAnimationFrame` example as above, but without
|
||||||
|
/// supporting early cancellation.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use wasm_bindgen::prelude::*;
|
||||||
|
///
|
||||||
|
/// #[wasm_bindgen]
|
||||||
|
/// extern "C" {
|
||||||
|
/// // We modify the binding to take an untyped `JsValue` since that is what
|
||||||
|
/// // is returned by `Closure::once_into_js`.
|
||||||
|
/// //
|
||||||
|
/// // If we were using the `web_sys` binding for `requestAnimationFrame`,
|
||||||
|
/// // then the call sites would cast the `JsValue` into a `&js_sys::Function`
|
||||||
|
/// // using `f.unchecked_ref::<js_sys::Function>()`. See the `web_sys`
|
||||||
|
/// // example above for details.
|
||||||
|
/// fn requestAnimationFrame(callback: JsValue);
|
||||||
|
///
|
||||||
|
/// #[wasm_bindgen(js_namespace = console)]
|
||||||
|
/// fn log(s: &str);
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// // A type that will log a message when it is dropped.
|
||||||
|
/// struct LogOnDrop(&'static str);
|
||||||
|
/// impl Drop for LogOnDrop {
|
||||||
|
/// fn drop(&mut self) {
|
||||||
|
/// log(self.0);
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// #[wasm_bindgen]
|
||||||
|
/// pub fn run() {
|
||||||
|
/// // We are using `Closure::once_into_js` which takes a `FnOnce` and
|
||||||
|
/// // converts it into a JavaScript function, which is returned as a
|
||||||
|
/// // `JsValue`.
|
||||||
|
/// let fired = LogOnDrop("animation frame fired");
|
||||||
|
/// let cb = Closure::once_into_js(move || drop(fired));
|
||||||
|
///
|
||||||
|
/// // Schedule the animation frame!
|
||||||
|
/// requestAnimationFrame(cb);
|
||||||
|
///
|
||||||
|
/// // No need to worry about whether or not we drop a `Closure`
|
||||||
|
/// // here or return some sort of handle to JS!
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
pub struct Closure<T: ?Sized> {
|
pub struct Closure<T: ?Sized> {
|
||||||
@ -109,39 +250,47 @@ union FatPtr<T: ?Sized> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Closure<T>
|
impl<T> Closure<T>
|
||||||
where T: ?Sized + WasmClosure,
|
where
|
||||||
|
T: ?Sized + WasmClosure,
|
||||||
{
|
{
|
||||||
/// Creates a new instance of `Closure` from the provided Rust closure.
|
/// A more ergonomic version of `Closure::wrap` that does the boxing and
|
||||||
///
|
/// cast to trait object for you.
|
||||||
/// Note that the closure provided here, `F`, has a few requirements
|
|
||||||
/// associated with it:
|
|
||||||
///
|
|
||||||
/// * It must implement `Fn` or `FnMut`
|
|
||||||
/// * It must be `'static`, aka no stack references (use the `move` keyword)
|
|
||||||
/// * It can have at most 7 arguments
|
|
||||||
/// * Its arguments and return values are all wasm types like u32/f64.
|
|
||||||
///
|
|
||||||
/// This is unfortunately pretty restrictive for now but hopefully some of
|
|
||||||
/// these restrictions can be lifted in the future!
|
|
||||||
///
|
///
|
||||||
/// *This method requires the `nightly` feature of the `wasm-bindgen` crate
|
/// *This method requires the `nightly` feature of the `wasm-bindgen` crate
|
||||||
/// to be enabled, meaning this is a nightly-only API. Users on stable
|
/// to be enabled, meaning this is a nightly-only API. Users on stable
|
||||||
/// should use `Closure::wrap`.*
|
/// should use `Closure::wrap`.*
|
||||||
#[cfg(feature = "nightly")]
|
#[cfg(feature = "nightly")]
|
||||||
pub fn new<F>(t: F) -> Closure<T>
|
pub fn new<F>(t: F) -> Closure<T>
|
||||||
where F: Unsize<T> + 'static
|
where
|
||||||
|
F: Unsize<T> + 'static,
|
||||||
{
|
{
|
||||||
Closure::wrap(Box::new(t) as Box<T>)
|
Closure::wrap(Box::new(t) as Box<T>)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A mostly internal function to wrap a boxed closure inside a `Closure`
|
/// Creates a new instance of `Closure` from the provided boxed Rust
|
||||||
/// type.
|
/// function.
|
||||||
///
|
///
|
||||||
/// This is the function where the JS closure is manufactured.
|
/// Note that the closure provided here, `Box<T>`, has a few requirements
|
||||||
|
/// associated with it:
|
||||||
|
///
|
||||||
|
/// * It must implement `Fn` or `FnMut` (for `FnOnce` functions see
|
||||||
|
/// `Closure::once` and `Closure::once_into_js`).
|
||||||
|
///
|
||||||
|
/// * It must be `'static`, aka no stack references (use the `move`
|
||||||
|
/// keyword).
|
||||||
|
///
|
||||||
|
/// * It can have at most 7 arguments.
|
||||||
|
///
|
||||||
|
/// * Its arguments and return values are all types that can be shared with
|
||||||
|
/// JS (i.e. have `#[wasm_bindgen]` annotations or are simple numbers,
|
||||||
|
/// etc.)
|
||||||
pub fn wrap(mut data: Box<T>) -> Closure<T> {
|
pub fn wrap(mut data: Box<T>) -> Closure<T> {
|
||||||
assert_eq!(mem::size_of::<*const T>(), mem::size_of::<FatPtr<T>>());
|
assert_eq!(mem::size_of::<*const T>(), mem::size_of::<FatPtr<T>>());
|
||||||
let (a, b) = unsafe {
|
let (a, b) = unsafe {
|
||||||
FatPtr { ptr: &mut *data as *mut T }.fields
|
FatPtr {
|
||||||
|
ptr: &mut *data as *mut T,
|
||||||
|
}
|
||||||
|
.fields
|
||||||
};
|
};
|
||||||
|
|
||||||
// Here we need to create a `JsValue` with the data and `T::invoke()`
|
// Here we need to create a `JsValue` with the data and `T::invoke()`
|
||||||
@ -194,20 +343,11 @@ impl<T> Closure<T>
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline(never)]
|
#[inline(never)]
|
||||||
unsafe fn breaks_if_inlined<T: WasmClosure + ?Sized>(
|
unsafe fn breaks_if_inlined<T: WasmClosure + ?Sized>(a: usize, b: usize) -> u32 {
|
||||||
a: usize,
|
super::__wbindgen_describe_closure(a as u32, b as u32, describe::<T> as u32)
|
||||||
b: usize,
|
|
||||||
) -> u32 {
|
|
||||||
super::__wbindgen_describe_closure(
|
|
||||||
a as u32,
|
|
||||||
b as u32,
|
|
||||||
describe::<T> as u32,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let idx = unsafe {
|
let idx = unsafe { breaks_if_inlined::<T>(a, b) };
|
||||||
breaks_if_inlined::<T>(a, b)
|
|
||||||
};
|
|
||||||
|
|
||||||
Closure {
|
Closure {
|
||||||
js: ManuallyDrop::new(JsValue::_new(idx)),
|
js: ManuallyDrop::new(JsValue::_new(idx)),
|
||||||
@ -234,6 +374,84 @@ impl<T> Closure<T>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NB: we use a specific `T` for this `Closure<T>` impl block to avoid every
|
||||||
|
// call site having to provide an explicit, turbo-fished type like
|
||||||
|
// `Closure::<FnOnce()>::once(...)`.
|
||||||
|
impl Closure<FnOnce()> {
|
||||||
|
/// Create a `Closure` from a function that can only be called once.
|
||||||
|
///
|
||||||
|
/// Since we have no way of enforcing that JS cannot attempt to call this
|
||||||
|
/// `FnOne(A...) -> R` more than once, this produces a `Closure<FnMut(A...)
|
||||||
|
/// -> R>` that will dynamically throw a JavaScript error if called more
|
||||||
|
/// than once.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust,no_run
|
||||||
|
/// use wasm_bindgen::prelude::*;
|
||||||
|
///
|
||||||
|
/// // Create an non-`Copy`, owned `String`.
|
||||||
|
/// let mut s = String::from("Hello");
|
||||||
|
///
|
||||||
|
/// // Close over `s`. Since `f` returns `s`, it is `FnOnce` and can only be
|
||||||
|
/// // called once. If it was called a second time, it wouldn't have any `s`
|
||||||
|
/// // to work with anymore!
|
||||||
|
/// let f = move || {
|
||||||
|
/// s += ", World!";
|
||||||
|
/// s
|
||||||
|
/// };
|
||||||
|
///
|
||||||
|
/// // Create a `Closure` from `f`. Note that the `Closure`'s type parameter
|
||||||
|
/// // is `FnMut`, even though `f` is `FnOnce`.
|
||||||
|
/// let closure: Closure<FnMut() -> String> = Closure::once(f);
|
||||||
|
/// ```
|
||||||
|
pub fn once<F, A, R>(fn_once: F) -> Closure<F::FnMut>
|
||||||
|
where
|
||||||
|
F: 'static + WasmClosureFnOnce<A, R>,
|
||||||
|
{
|
||||||
|
Closure::wrap(fn_once.into_fn_mut())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert a `FnOnce(A...) -> R` into a JavaScript `Function` object.
|
||||||
|
///
|
||||||
|
/// If the JavaScript function is invoked more than once, it will throw an
|
||||||
|
/// exception.
|
||||||
|
///
|
||||||
|
/// Unlike `Closure::once`, this does *not* return a `Closure` that can be
|
||||||
|
/// dropped before the function is invoked to deallocate the closure. The
|
||||||
|
/// only way the `FnOnce` is deallocated is by calling the JavaScript
|
||||||
|
/// function. If the JavaScript function is never called then the `FnOnce`
|
||||||
|
/// and everything it closes over will leak.
|
||||||
|
///
|
||||||
|
/// ```rust,ignore
|
||||||
|
/// use js_sys;
|
||||||
|
/// use wasm_bindgen::{prelude::*, JsCast};
|
||||||
|
///
|
||||||
|
/// let f = Closure::once_into_js(move || {
|
||||||
|
/// // ...
|
||||||
|
/// });
|
||||||
|
///
|
||||||
|
/// assert!(f.is_instance_of::<js_sys::Function>());
|
||||||
|
/// ```
|
||||||
|
pub fn once_into_js<F, A, R>(fn_once: F) -> JsValue
|
||||||
|
where
|
||||||
|
F: 'static + WasmClosureFnOnce<A, R>,
|
||||||
|
{
|
||||||
|
fn_once.into_js_function()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A trait for converting an `FnOnce(A...) -> R` into a `FnMut(A...) -> R` that
|
||||||
|
/// will throw if ever called more than once.
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub trait WasmClosureFnOnce<A, R>: 'static {
|
||||||
|
type FnMut: ?Sized + 'static + WasmClosure;
|
||||||
|
|
||||||
|
fn into_fn_mut(self) -> Box<Self::FnMut>;
|
||||||
|
|
||||||
|
fn into_js_function(self) -> JsValue;
|
||||||
|
}
|
||||||
|
|
||||||
impl<T: ?Sized> AsRef<JsValue> for Closure<T> {
|
impl<T: ?Sized> AsRef<JsValue> for Closure<T> {
|
||||||
fn as_ref(&self) -> &JsValue {
|
fn as_ref(&self) -> &JsValue {
|
||||||
&self.js
|
&self.js
|
||||||
@ -241,7 +459,8 @@ impl<T: ?Sized> AsRef<JsValue> for Closure<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<T> WasmDescribe for Closure<T>
|
impl<T> WasmDescribe for Closure<T>
|
||||||
where T: WasmClosure + ?Sized,
|
where
|
||||||
|
T: WasmClosure + ?Sized,
|
||||||
{
|
{
|
||||||
fn describe() {
|
fn describe() {
|
||||||
inform(ANYREF);
|
inform(ANYREF);
|
||||||
@ -250,7 +469,8 @@ impl<T> WasmDescribe for Closure<T>
|
|||||||
|
|
||||||
// `Closure` can only be passed by reference to imports.
|
// `Closure` can only be passed by reference to imports.
|
||||||
impl<'a, T> IntoWasmAbi for &'a Closure<T>
|
impl<'a, T> IntoWasmAbi for &'a Closure<T>
|
||||||
where T: WasmClosure + ?Sized,
|
where
|
||||||
|
T: WasmClosure + ?Sized,
|
||||||
{
|
{
|
||||||
type Abi = u32;
|
type Abi = u32;
|
||||||
|
|
||||||
@ -270,7 +490,8 @@ fn _check() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Drop for Closure<T>
|
impl<T> Drop for Closure<T>
|
||||||
where T: ?Sized,
|
where
|
||||||
|
T: ?Sized,
|
||||||
{
|
{
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
unsafe {
|
unsafe {
|
||||||
@ -345,7 +566,7 @@ macro_rules! doit {
|
|||||||
a: usize,
|
a: usize,
|
||||||
b: usize,
|
b: usize,
|
||||||
) {
|
) {
|
||||||
debug_assert!(a != 0);
|
debug_assert!(a != 0, "should never destroy a Fn whose pointer is 0");
|
||||||
drop(Box::from_raw(FatPtr::<Fn($($var,)*) -> R> {
|
drop(Box::from_raw(FatPtr::<Fn($($var,)*) -> R> {
|
||||||
fields: (a, b)
|
fields: (a, b)
|
||||||
}.ptr));
|
}.ptr));
|
||||||
@ -392,7 +613,7 @@ macro_rules! doit {
|
|||||||
a: usize,
|
a: usize,
|
||||||
b: usize,
|
b: usize,
|
||||||
) {
|
) {
|
||||||
debug_assert!(a != 0);
|
debug_assert!(a != 0, "should never destroy a FnMut whose pointer is 0");
|
||||||
drop(Box::from_raw(FatPtr::<FnMut($($var,)*) -> R> {
|
drop(Box::from_raw(FatPtr::<FnMut($($var,)*) -> R> {
|
||||||
fields: (a, b)
|
fields: (a, b)
|
||||||
}.ptr));
|
}.ptr));
|
||||||
@ -402,6 +623,56 @@ macro_rules! doit {
|
|||||||
<&mut Self>::describe();
|
<&mut Self>::describe();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
impl<T, $($var,)* R> WasmClosureFnOnce<($($var),*), R> for T
|
||||||
|
where T: 'static + FnOnce($($var),*) -> R,
|
||||||
|
$($var: FromWasmAbi + 'static,)*
|
||||||
|
R: ReturnWasmAbi + 'static
|
||||||
|
{
|
||||||
|
type FnMut = FnMut($($var),*) -> R;
|
||||||
|
|
||||||
|
fn into_fn_mut(self) -> Box<Self::FnMut> {
|
||||||
|
let mut me = Some(self);
|
||||||
|
Box::new(move |$($var),*| {
|
||||||
|
let me = me.take().expect_throw("FnOnce called more than once");
|
||||||
|
me($($var),*)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn into_js_function(self) -> JsValue {
|
||||||
|
use std::rc::Rc;
|
||||||
|
use __rt::WasmRefCell;
|
||||||
|
|
||||||
|
let mut me = Some(self);
|
||||||
|
|
||||||
|
let rc1 = Rc::new(WasmRefCell::new(None));
|
||||||
|
let rc2 = rc1.clone();
|
||||||
|
|
||||||
|
let closure = Closure::wrap(Box::new(move |$($var),*| {
|
||||||
|
// Invoke ourself and get the result.
|
||||||
|
let me = me.take().expect_throw("FnOnce called more than once");
|
||||||
|
let result = me($($var),*);
|
||||||
|
|
||||||
|
// And then drop the `Rc` holding this function's `Closure`
|
||||||
|
// alive.
|
||||||
|
debug_assert_eq!(Rc::strong_count(&rc2), 1);
|
||||||
|
let option_closure = rc2.borrow_mut().take();
|
||||||
|
debug_assert!(option_closure.is_some());
|
||||||
|
drop(option_closure);
|
||||||
|
|
||||||
|
result
|
||||||
|
}) as Box<FnMut($($var),*) -> R>);
|
||||||
|
|
||||||
|
let js_val = closure.as_ref().clone();
|
||||||
|
|
||||||
|
*rc1.borrow_mut() = Some(closure);
|
||||||
|
debug_assert_eq!(Rc::strong_count(&rc1), 2);
|
||||||
|
drop(rc1);
|
||||||
|
|
||||||
|
js_val
|
||||||
|
}
|
||||||
|
}
|
||||||
)*)
|
)*)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -657,7 +657,9 @@ pub fn throw_val(s: JsValue) -> ! {
|
|||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```no_run
|
/// ```
|
||||||
|
/// use wasm_bindgen::prelude::*;
|
||||||
|
///
|
||||||
/// // If the value is `Option::Some` or `Result::Ok`, then we just get the
|
/// // If the value is `Option::Some` or `Result::Ok`, then we just get the
|
||||||
/// // contained `T` value.
|
/// // contained `T` value.
|
||||||
/// let x = Some(42);
|
/// let x = Some(42);
|
||||||
|
@ -102,3 +102,14 @@ exports.drop_during_call_call = () => DROP_DURING_CALL();
|
|||||||
exports.js_test_closure_returner = () => {
|
exports.js_test_closure_returner = () => {
|
||||||
wasm.closure_returner().someKey();
|
wasm.closure_returner().someKey();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
exports.calling_it_throws = a => {
|
||||||
|
try {
|
||||||
|
a();
|
||||||
|
return false;
|
||||||
|
} catch(_) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.call_val = f => f();
|
||||||
|
115
tests/wasm/closures.rs
Normal file → Executable file
115
tests/wasm/closures.rs
Normal file → Executable file
@ -25,6 +25,24 @@ extern "C" {
|
|||||||
fn many_arity_call6(a: &Closure<Fn(u32, u32, u32, u32, u32)>);
|
fn many_arity_call6(a: &Closure<Fn(u32, u32, u32, u32, u32)>);
|
||||||
fn many_arity_call7(a: &Closure<Fn(u32, u32, u32, u32, u32, u32)>);
|
fn many_arity_call7(a: &Closure<Fn(u32, u32, u32, u32, u32, u32)>);
|
||||||
fn many_arity_call8(a: &Closure<Fn(u32, u32, u32, u32, u32, u32, u32)>);
|
fn many_arity_call8(a: &Closure<Fn(u32, u32, u32, u32, u32, u32, u32)>);
|
||||||
|
|
||||||
|
#[wasm_bindgen(js_name = many_arity_call1)]
|
||||||
|
fn many_arity_call_mut1(a: &Closure<FnMut()>);
|
||||||
|
#[wasm_bindgen(js_name = many_arity_call2)]
|
||||||
|
fn many_arity_call_mut2(a: &Closure<FnMut(u32)>);
|
||||||
|
#[wasm_bindgen(js_name = many_arity_call3)]
|
||||||
|
fn many_arity_call_mut3(a: &Closure<FnMut(u32, u32)>);
|
||||||
|
#[wasm_bindgen(js_name = many_arity_call4)]
|
||||||
|
fn many_arity_call_mut4(a: &Closure<FnMut(u32, u32, u32)>);
|
||||||
|
#[wasm_bindgen(js_name = many_arity_call5)]
|
||||||
|
fn many_arity_call_mut5(a: &Closure<FnMut(u32, u32, u32, u32)>);
|
||||||
|
#[wasm_bindgen(js_name = many_arity_call6)]
|
||||||
|
fn many_arity_call_mut6(a: &Closure<FnMut(u32, u32, u32, u32, u32)>);
|
||||||
|
#[wasm_bindgen(js_name = many_arity_call7)]
|
||||||
|
fn many_arity_call_mut7(a: &Closure<FnMut(u32, u32, u32, u32, u32, u32)>);
|
||||||
|
#[wasm_bindgen(js_name = many_arity_call8)]
|
||||||
|
fn many_arity_call_mut8(a: &Closure<FnMut(u32, u32, u32, u32, u32, u32, u32)>);
|
||||||
|
|
||||||
#[wasm_bindgen(js_name = many_arity_call1)]
|
#[wasm_bindgen(js_name = many_arity_call1)]
|
||||||
fn many_arity_stack1(a: &Fn());
|
fn many_arity_stack1(a: &Fn());
|
||||||
#[wasm_bindgen(js_name = many_arity_call2)]
|
#[wasm_bindgen(js_name = many_arity_call2)]
|
||||||
@ -65,6 +83,13 @@ extern "C" {
|
|||||||
fn drop_during_call_call();
|
fn drop_during_call_call();
|
||||||
|
|
||||||
fn js_test_closure_returner();
|
fn js_test_closure_returner();
|
||||||
|
|
||||||
|
fn calling_it_throws(a: &Closure<FnMut()>) -> bool;
|
||||||
|
|
||||||
|
fn call_val(f: &JsValue);
|
||||||
|
|
||||||
|
#[wasm_bindgen(js_name = calling_it_throws)]
|
||||||
|
fn call_val_throws(f: &JsValue) -> bool;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen_test]
|
#[wasm_bindgen_test]
|
||||||
@ -122,6 +147,44 @@ fn many_arity() {
|
|||||||
assert_eq!((a, b, c, d, e, f, g), (1, 2, 3, 4, 5, 6, 7))
|
assert_eq!((a, b, c, d, e, f, g), (1, 2, 3, 4, 5, 6, 7))
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
let s = String::new();
|
||||||
|
many_arity_call_mut1(&Closure::once(move || drop(s)));
|
||||||
|
let s = String::new();
|
||||||
|
many_arity_call_mut2(&Closure::once(move |a| {
|
||||||
|
drop(s);
|
||||||
|
assert_eq!(a, 1);
|
||||||
|
}));
|
||||||
|
let s = String::new();
|
||||||
|
many_arity_call_mut3(&Closure::once(move |a, b| {
|
||||||
|
drop(s);
|
||||||
|
assert_eq!((a, b), (1, 2));
|
||||||
|
}));
|
||||||
|
let s = String::new();
|
||||||
|
many_arity_call_mut4(&Closure::once(move |a, b, c| {
|
||||||
|
drop(s);
|
||||||
|
assert_eq!((a, b, c), (1, 2, 3));
|
||||||
|
}));
|
||||||
|
let s = String::new();
|
||||||
|
many_arity_call_mut5(&Closure::once(move |a, b, c, d| {
|
||||||
|
drop(s);
|
||||||
|
assert_eq!((a, b, c, d), (1, 2, 3, 4));
|
||||||
|
}));
|
||||||
|
let s = String::new();
|
||||||
|
many_arity_call_mut6(&Closure::once(move |a, b, c, d, e| {
|
||||||
|
drop(s);
|
||||||
|
assert_eq!((a, b, c, d, e), (1, 2, 3, 4, 5));
|
||||||
|
}));
|
||||||
|
let s = String::new();
|
||||||
|
many_arity_call_mut7(&Closure::once(move |a, b, c, d, e, f| {
|
||||||
|
drop(s);
|
||||||
|
assert_eq!((a, b, c, d, e, f), (1, 2, 3, 4, 5, 6));
|
||||||
|
}));
|
||||||
|
let s = String::new();
|
||||||
|
many_arity_call_mut8(&Closure::once(move |a, b, c, d, e, f, g| {
|
||||||
|
drop(s);
|
||||||
|
assert_eq!((a, b, c, d, e, f, g), (1, 2, 3, 4, 5, 6, 7));
|
||||||
|
}));
|
||||||
|
|
||||||
many_arity_stack1(&(|| {}));
|
many_arity_stack1(&(|| {}));
|
||||||
many_arity_stack2(&(|a| assert_eq!(a, 1)));
|
many_arity_stack2(&(|a| assert_eq!(a, 1)));
|
||||||
many_arity_stack3(&(|a, b| assert_eq!((a, b), (1, 2))));
|
many_arity_stack3(&(|a, b| assert_eq!((a, b), (1, 2))));
|
||||||
@ -134,6 +197,58 @@ fn many_arity() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct Dropper(Rc<Cell<bool>>);
|
||||||
|
impl Drop for Dropper {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
assert!(!self.0.get());
|
||||||
|
self.0.set(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen_test]
|
||||||
|
fn call_fn_once_twice() {
|
||||||
|
let dropped = Rc::new(Cell::new(false));
|
||||||
|
let dropper = Dropper(dropped.clone());
|
||||||
|
let called = Rc::new(Cell::new(false));
|
||||||
|
|
||||||
|
let c = Closure::once({
|
||||||
|
let called = called.clone();
|
||||||
|
move || {
|
||||||
|
assert!(!called.get());
|
||||||
|
called.set(true);
|
||||||
|
drop(dropper);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
many_arity_call_mut1(&c);
|
||||||
|
assert!(called.get());
|
||||||
|
assert!(dropped.get());
|
||||||
|
|
||||||
|
assert!(calling_it_throws(&c));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen_test]
|
||||||
|
fn once_into_js() {
|
||||||
|
let dropped = Rc::new(Cell::new(false));
|
||||||
|
let dropper = Dropper(dropped.clone());
|
||||||
|
let called = Rc::new(Cell::new(false));
|
||||||
|
|
||||||
|
let f = Closure::once_into_js({
|
||||||
|
let called = called.clone();
|
||||||
|
move || {
|
||||||
|
assert!(!called.get());
|
||||||
|
called.set(true);
|
||||||
|
drop(dropper);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
call_val(&f);
|
||||||
|
assert!(called.get());
|
||||||
|
assert!(dropped.get());
|
||||||
|
|
||||||
|
assert!(call_val_throws(&f));
|
||||||
|
}
|
||||||
|
|
||||||
#[wasm_bindgen_test]
|
#[wasm_bindgen_test]
|
||||||
fn long_lived_dropping() {
|
fn long_lived_dropping() {
|
||||||
let hit = Rc::new(Cell::new(false));
|
let hit = Rc::new(Cell::new(false));
|
||||||
|
Loading…
x
Reference in New Issue
Block a user