Это хороший вопрос, и у него тоже есть нюанс! Стоит упомянуть пример замыканий в руководстве wasm-bindgen
(и раздел о передаче замыканий в JavaScript ), и было бы неплохо внести свой вклад в это как ну если надо!
Чтобы начать, вы можете сделать что-то вроде этого:
use wasm_bindgen::{Closure, JsValue};
#[wasm_bindgen]
pub fn start_game(
start_time: f64,
screen_width: f32,
screen_height: f32,
on_render: &js_sys::Function,
on_collision: &js_sys::Function,
) -> JsValue {
let cb = Closure::wrap(Box::new(move |time| {
time * 4.2
}) as Box<FnMut(f64) -> f64>);
// Extract the `JsValue` from this `Closure`, the handle
// on a JS function representing the closure
let ret = cb.as_ref().clone();
// Once `cb` is dropped it'll "neuter" the closure and
// cause invocations to throw a JS exception. Memory
// management here will come later, so just leak it
// for now.
cb.forget();
return ret;
}
Над возвращаемым значением находится просто старый JS-объект (здесь как JsValue
), и мы создаем его с типом Closure
, который вы уже видели. Это позволит вам быстро вернуть замыкание в JS, и вы также сможете вызывать его из JS.
Вы также спрашивали о хранении изменяемых объектов и тому подобного, и все это можно сделать с помощью обычных замыканий в Rust, захвата и т. Д. Например, объявление FnMut(f64) -> f64
выше является сигнатурой функции JS, и это может быть любым набором типов, таких как FnMut(String, MyCustomWasmBindgenType, f64) ->
Vec<u8>
, если вы действительно хотите. Для захвата локальных объектов вы можете сделать:
let mut camera = Camera::new();
let mut state = State::new();
let cb = Closure::wrap(Box::new(move |arg1, arg2| { // note the `move`
if arg1 {
camera.update(&arg2);
} else {
state.update(&arg2);
}
}) as Box<_>);
(или что-то в этом роде)
Здесь переменные camera
и state
будут принадлежать замыканию и удаляться одновременно. Больше информации о замыканиях можно найти в книге Rust .
Здесь также стоит кратко остановиться на аспекте управления памятью. в
В приведенном выше примере мы вызываем forget()
, что приводит к утечке памяти и может стать проблемой, если функция Rust вызывается много раз (так как это приведет к утечке большой памяти). Основная проблема заключается в том, что в куче WASM выделена память, на которую ссылается созданный объект функции JS. Эта распределенная память теоретически должна быть освобождена всякий раз, когда объект функции JS является GC'd, но у нас нет никакой возможности узнать, когда это произойдет (до WeakRef
существует !).
Тем временем мы выбрали альтернативную стратегию. Связанная память
освобождается всякий раз, когда сам тип Closure
отбрасывается, обеспечивая
детерминированное разрушение. Это, однако, затрудняет работу, так как нам нужно выяснить вручную, когда отбрасывать Closure
. Если forget
не подходит для вашего варианта использования, некоторые идеи для отбрасывания Closure
:
Во-первых, если JS-замыкание вызывается только один раз, тогда вы можете использовать Rc
/ RefCell
бросить Closure
внутри самого затвора (используя некоторый интерьер
изменчивость махинаций). Мы должны также в конце концов
обеспечить встроенную поддержку
для FnOnce
в wasm-bindgen
, а также!
Далее вы можете вернуть вспомогательный JS-объект в Rust с руководством free
метод. Например, #[wasm_bindgen]
-аннотированная оболочка. Эта обертка будет
затем необходимо освободить вручную в JS при необходимости.
Если вы можете обойтись, forget
, безусловно, самая простая вещь для
сейчас, но это определенно болевая точка! Мы не можем дождаться существования WeakRef
:)