Rust 如何使用wasm bindgen在闭包之间惯用地共享数据?

Rust 如何使用wasm bindgen在闭包之间惯用地共享数据?,rust,wasm-bindgen,Rust,Wasm Bindgen,在我的浏览器应用程序中,两个闭包访问存储在Rc中的数据。一个闭包可变地借用数据,而另一个闭包不变地借用数据。这两个闭包相互独立地调用,这偶尔会导致BorrowError或borrowMuterError 以下是我对MWE的尝试,尽管它使用未来人为地夸大错误发生的可能性: use std::cell::RefCell; use std::future::Future; use std::pin::Pin; use std::rc::Rc; use std::task::{Context, Poll

在我的浏览器应用程序中,两个闭包访问存储在
Rc
中的数据。一个闭包可变地借用数据,而另一个闭包不变地借用数据。这两个闭包相互独立地调用,这偶尔会导致
BorrowError
borrowMuterError

以下是我对MWE的尝试,尽管它使用未来人为地夸大错误发生的可能性:

use std::cell::RefCell;
use std::future::Future;
use std::pin::Pin;
use std::rc::Rc;
use std::task::{Context, Poll, Waker};
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsValue;

#[wasm_bindgen]
extern "C" {
    #[wasm_bindgen(js_namespace = console)]
    pub fn log(s: &str);
    #[wasm_bindgen(js_name = setTimeout)]
    fn set_timeout(closure: &Closure<dyn FnMut()>, millis: u32) -> i32;
    #[wasm_bindgen(js_name = setInterval)]
    fn set_interval(closure: &Closure<dyn FnMut()>, millis: u32) -> i32;
}

pub struct Counter(u32);

#[wasm_bindgen(start)]
pub async fn main() -> Result<(), JsValue> {
    console_error_panic_hook::set_once();

    let counter = Rc::new(RefCell::new(Counter(0)));

    let counter_clone = counter.clone();
    let log_closure = Closure::wrap(Box::new(move || {
        let c = counter_clone.borrow();
        log(&c.0.to_string());
    }) as Box<dyn FnMut()>);
    set_interval(&log_closure, 1000);
    log_closure.forget();

    let counter_clone = counter.clone();
    let increment_closure = Closure::wrap(Box::new(move || {
        let counter_clone = counter_clone.clone();
        wasm_bindgen_futures::spawn_local(async move {
            let mut c = counter_clone.borrow_mut();
            // In reality this future would be replaced by some other
            // time-consuming operation manipulating the borrowed data
            SleepFuture::new(5000).await;
            c.0 += 1;
        });
    }) as Box<dyn FnMut()>);
    set_timeout(&increment_closure, 3000);
    increment_closure.forget();

    Ok(())
}

struct SleepSharedState {
    waker: Option<Waker>,
    completed: bool,
    closure: Option<Closure<dyn FnMut()>>,
}

struct SleepFuture {
    shared_state: Rc<RefCell<SleepSharedState>>,
}

impl Future for SleepFuture {
    type Output = ();
    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
        let mut shared_state = self.shared_state.borrow_mut();
        if shared_state.completed {
            Poll::Ready(())
        } else {
            shared_state.waker = Some(cx.waker().clone());
            Poll::Pending
        }
    }
}

impl SleepFuture {
    fn new(duration: u32) -> Self {
        let shared_state = Rc::new(RefCell::new(SleepSharedState {
            waker: None,
            completed: false,
            closure: None,
        }));

        let state_clone = shared_state.clone();
        let closure = Closure::wrap(Box::new(move || {
            let mut state = state_clone.borrow_mut();
            state.completed = true;
            if let Some(waker) = state.waker.take() {
                waker.wake();
            }
        }) as Box<dyn FnMut()>);

        set_timeout(&closure, duration);

        shared_state.borrow_mut().closure = Some(closure);

        SleepFuture { shared_state }
    }
}
使用std::cell::RefCell;
使用std::future::future;
使用std::pin::pin;
使用std::rc::rc;
使用std::task::{Context,Poll,Waker};
使用wasm_bindgen::prelude::*;
使用wasm_bindgen::JsValue;
#[瓦斯穆宾根]
外部“C”{
#[wasm_bindgen(js_namespace=console)]
发布fn日志(s:&str);
#[wasm_bindgen(js_name=setTimeout)]
fn设置\u超时(闭包:&闭包,毫秒:u32)->i32;
#[wasm_bindgen(js_name=setInterval)]
fn设置间隔(闭合:&closure,millis:u32)->i32;
}
发布结构计数器(u32);
#[wasm_bindgen(start)]
pub async fn main()->结果{
console_error_panic_hook::set_once();
让计数器=Rc::new(RefCell::new(计数器(0));
让counter_clone=counter.clone();
让log_closure=closure::wrap(Box::new(move | |){
设c=计数器_clone.borrow();
日志(&c.0.to_string());
})如盒子);
设置\u间隔(&log\u closure,1000);
log_closure.forget();
让counter_clone=counter.clone();
让增量|closure=closure::wrap(Box::new(move ||){
让counter_clone=counter_clone.clone();
wasm_bindgen_futures::spawn_local(异步移动{
让mut c=counter_clone.borrow_mut();
//事实上,这一未来将被其他未来所取代
//操作借用数据的耗时操作
新的(5000)。等待;
c、 0+=1;
});
})如盒子);
设置\u超时(&increment\u closure,3000);
增量_闭包。忘记();
好(())
}
结构SleepSharedState{
唤醒器:选项,
完成:布尔,
关闭:选项,
}
结构未来{
共享状态:Rc,
}
未来的未来{
类型输出=();

fn poll(self:Pin,cx:&mut Context独立于Rust的借用语义来考虑这个问题。您有一个正在更新某些共享状态的长时间运行的操作

  • 如果你使用线程,你会怎么做?你会把共享状态放在锁后面。
    RefCell
    就像一个锁,只是你不能在解锁时阻止它——但是你可以通过使用某种消息传递来唤醒读卡器来模拟阻止

  • 如果您使用的是纯JavaScript,您会怎么做?您不会自动拥有任何类似于RefCell的内容,因此:

    • 当操作仍在进行时,可以安全地读取状态(在并发而非并行意义上):在这种情况下,通过在
      等待
      边界上不保持单个
      RefMut
      (借用的结果)(
)处于活动状态来模拟该状态
  • 读取状态是不安全的:您可以编写如上所述的锁,或者进行安排,以便在操作完成时只写入一次,并且在此之前,长期运行的操作具有自己的私有状态,不与应用程序的其余部分共享(因此不可能存在
    借用错误
    冲突)

  • 考虑您的应用程序实际需要什么,并选择合适的解决方案。实现这些解决方案中的任何一个都很可能涉及使用额外的内部可变对象进行通信。

    回答得很好,谢谢。我的应用程序在
    等待期间不再持有
    RefMut
    。关于阻塞仿真,你知道我可以引用哪些资源吗?到目前为止,我在Rust中还没有成功实现。@user24182它在概念上相当简单,但有一些微妙的细节;你需要一个某种回调列表(对该对象的操作被“阻止”),当该资源被“解锁”时,你调用其中一个或全部,让他们知道“锁定”它与任何类型的“状态更改通知”机制没有本质区别。