Rust Wasm中Peer成员变量num_channel_opened无法持久更新问题
问题描述
我在开发WebRTC相关的Rust/Wasm代码时遇到了一个问题:每次WebRTC通道打开时触发的回调里,无法持久化递增Peer结构体中的num_channel_opened变量(默认值为0)。我共有三个通道,期望每次通道打开时该变量依次递增为1、2、3,但实际每次输出都是1,之前的值无法保存。
我的Peer结构体和实现代码如下:
#[wasm_bindgen] #[derive(Clone)] struct Peer { num_channel_opened:i16 } #[wasm_bindgen] impl Peer { /* ... */ fn new() { /* ... */ } fn on_channel_opened () { /* ... */ } /* ... */ }
当前的监听器函数实现:
fn on_channel_opened ( &self ) -> Closure<dyn FnMut(Event)>{ let mut selfCell = Rc::new(RefCell::new( self.clone() ) ); Closure::wrap( Box::new( move |ev: Event | { let mut selfClone = selfCell.clone(); (*selfClone).borrow_mut().num_channels_opened += 1; console_warn!("number of channel opened {:?}", ( *selfClone ).borrow().num_channels_opened ); } }) as Box<dyn FnMut(Event)> ) }
对应的JavaScript实现可以正常工作,代码如下:
class Peer { num_channels_opened = 0; peer; constructor () { this.peer = new RTCPeerConnection(); //create channels //wait to receive remote channels //attach on_channel_opened to received remote channels //this would look like //channel.onopen = this.on_channel_opened; } on_channel_opened = () => { this.num_channel_opened++; console.log( this.num_channel_opened ) // 1, 2, 3...N channels } }
问题原因
你的Rust代码中,每次调用on_channel_opened(&self)时,都会执行self.clone()创建一个新的Peer实例,然后把这个实例包裹在新的Rc<RefCell>中。这意味着每个通道的onopen回调持有的都是独立的Peer副本,每次递增的都是各自副本里的num_channel_opened,自然无法共享状态,所以每次输出都是1。
而JavaScript的箭头函数会捕获当前实例的this,所有回调共享同一个Peer实例的引用,因此能正确累加变量。
解决方案
要让所有回调共享同一个Peer实例的状态,你需要让Peer本身被包裹在Rc<RefCell>中,确保所有回调都指向同一个实例。以下是修正后的完整代码:
修正后的代码示例
use std::cell::RefCell; use std::rc::Rc; use wasm_bindgen::prelude::*; use web_sys::Event; #[wasm_bindgen] #[derive(Clone)] struct Peer { num_channel_opened: i16, closures: Vec<Closure<dyn FnMut(Event)>>, } #[wasm_bindgen] impl Peer { pub fn new() -> Rc<RefCell<Self>> { Rc::new(RefCell::new(Self { num_channel_opened: 0, closures: Vec::new(), })) } pub fn on_channel_opened(self: &Rc<RefCell<Self>>) -> &Closure<dyn FnMut(Event)> { // 捕获共享的Rc引用,所有回调共享同一个Peer实例 let self_rc = self.clone(); let closure = Closure::wrap(Box::new(move |_ev: Event| { let mut peer = self_rc.borrow_mut(); peer.num_channel_opened += 1; console_warn!( "number of channel opened {:?}", peer.num_channel_opened ); }) as Box<dyn FnMut(Event)>); // 将Closure存储到Peer实例中,避免被JavaScript GC回收 self.borrow_mut().closures.push(closure); self.borrow().closures.last().unwrap() } // 其他业务方法... }
使用方式
创建Peer实例并绑定通道回调时,直接使用共享引用:
let peer = Peer::new(); // 假设获取到WebRTC通道实例channel channel.set_onopen(Some(peer.on_channel_opened().as_ref().unchecked_ref()));
关键注意点
- 保留Closure引用:Wasm中的
Closure如果没有被持有,会被JavaScript的GC回收导致回调失效,因此需要在Peer结构体中添加closures字段存储所有创建的回调。
这样修改后,所有通道的回调都会共享同一个Peer实例的状态,num_channel_opened就能正确递增为1、2、3了。
内容的提问来源于stack exchange,提问作者JSmith
相关产品推荐
相关产品推荐

