You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.23 22:24:59