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

Rust WASM中Socket.io回调调用Reiniciar的Send trait错误

问题:WASM环境下Rust中socket.io回调调用Game方法触发Send trait错误

在WASM环境用Rust开发时,尝试在socket.io的回调里调用Game结构体的Reiniciar可变方法,多次尝试均失败:

  • 用self::Reiniciar():调用错误
  • 改为Game::Reiniciar():仍报错
  • 尝试Game::Reiniciar(&mut self):触发错误E0277,提示*mut u8无法安全跨线程发送,根源是JsValue、Window等类型未实现Send trait

相关代码

最初尝试的回调代码:

self.socket = Some(
    SocketBuilder::new(self.host.clone())
        .on("connect", |_,_|{})    
        .on("REBOOT", | _,_| {
            self::Reiniciar()     
        })
        .on("message", callback) 
        .on("error", |err, _| eprintln!("Error: {:#?}", err))        
        .connect()        
        .expect("Connection failed")
);

修改为Game调用后的代码:

self.socket = Some(
    SocketBuilder::new(self.host.clone())
        .on("connect", |_,_|{})    
        .on("REBOOT", | _,_| {
            Game::Reiniciar()     
        })
        .on("message", callback) 
        .on("error", |err, _| eprintln!("Error: {:#?}", err))        
        .connect()        
        .expect("Connection failed")
);

Game结构体完整实现:

impl Game {
    fn Reiniciar(&mut self){
        let document = self.html.document.clone();
        document.get_element_by_id("tabuleiro").unwrap().set_inner_html("");

        let cards = document.query_selector_all("#hand > div:not(#wait) ")
            .unwrap()
            .dyn_into::<web_sys::NodeList>()
            .unwrap();

        let mut i = 0;
        while i < 101 {
            cards.item(i)
                .unwrap()
                .dyn_into::<web_sys::HtmlElement>()
                .unwrap()
                .parent_node()
                .unwrap()
                .remove_child(&cards.item(i).unwrap());
            i += 1
        }
    }

    fn listen(&mut self){
        let callback = | payload: Payload, _socket: Socket| {        
            match payload {            
                 Payload::String(str) => 
                     println!("{}", str[1..str.len() - 1].to_string()),
                 Payload::Binary(bin_data) => 
                     println!("{:?}",bin_data),
            }    
        }; 

        self.socket = Some(
            SocketBuilder::new(self.host.clone())
                .on("connect", |_,_|{})    
                .on("REBOOT", | _,_| {
                    Game::Reiniciar()     
                })
                .on("message", callback) 
                .on("error", |err, _| eprintln!("Error: {:#?}", err))        
                .connect()        
                .expect("Connection failed")
        );
    }

    fn new<T: Into<String>, U: Into<bool>>(host: T, debug: U) ->Self{
        let html = html::html::Html::new();
        Self {
            ultimo: -1,
            jogador: -1,
            debug: debug.into(),
            host: host.into(),
            socket: None,
            html
        }
    }
}

错误信息

传入&mut self后的报错:

error[E0277]: `*mut u8` cannot be sent between threads safely
   --> src/lib.rs:102:27
    |
102 |               .on("REBOOT", | _,_| {
    |                --           ^-----
    |                |            |
    |  ______________|____________within this `[closure@src/lib.rs:102:27: 102:33]`
    | |              |
    | |              required by a bound introduced by this call
103 | |                 Game::Reiniciar(&mut self)
104 | |             })
    | |_____________^ `*mut u8` cannot be sent between threads safely
    |
    = help: within `[closure@src/lib.rs:102:27: 102:33]`, the trait `Send` is not implemented for `*mut u8`
    = note: required because it appears within the type `PhantomData<*mut u8>`
    = note: required because it appears within the type `JsValue`
    = note: required because it appears within the type `Object`
    = note: required because it appears within the type `EventTarget`
    = note: required because it appears within the type `Window`

解决方案

错误根源

socket.io的Rust库要求回调闭包实现Send trait,因为它假设回调可能在不同线程执行。但WASM环境下,JsValue、Window等与浏览器API绑定的类型都没有实现Send——这是因为浏览器DOM操作只能在主线程执行,跨线程传递这些类型是不安全的。当你在回调里捕获&mut self时,self包含了这些非Send类型,导致闭包不符合Send约束,触发E0277错误。

可行方案

方案1:将Game实例包装为Arc<Mutex>

通过Arc<Mutex>实现线程安全的共享访问,这样闭包可以捕获Arc<Mutex<Game>>实例,满足Send约束。注意WASM实际是单线程模型,Mutex不会带来真正的线程竞争,只是用来满足类型约束。

修改步骤:

  1. 先为Game实现Clone trait(web_sys下的类型大多已实现Clone,直接派生即可):
#[derive(Clone)]
struct Game {
    // ... 原有字段
}
  1. 在listen方法中包装实例并传入回调:
fn listen(&mut self){
    let callback = | payload: Payload, _socket: Socket| {        
        match payload {            
             Payload::String(str) => 
                 println!("{}", str[1..str.len() - 1].to_string()),
             Payload::Binary(bin_data) => 
                 println!("{:?}",bin_data),
        }    
    }; 

    // 包装为Arc<Mutex>
    let game = Arc::new(Mutex::new(self.clone()));

    self.socket = Some(
        SocketBuilder::new(self.host.clone())
            .on("connect", |_,_|{})    
            .on("REBOOT", move | _,_| {
                // 获取锁并调用方法
                let mut game = game.lock().unwrap();
                game.Reiniciar();
            })
            .on("message", callback) 
            .on("error", |err, _| eprintln!("Error: {:#?}", err))        
            .connect()        
            .expect("Connection failed")
    );
}

方案2:将回调逻辑调度到主线程执行

因为浏览器DOM操作必须在主线程,直接用web_sys的queue_microtask把Reiniciar的调用放到主线程队列中。这样回调闭包不需要捕获&mut self,而是通过全局实例或者Arc传递,同时避开跨线程的Send约束。

修改示例:

// 假设Game实例已用Arc<Mutex>全局存储或传递
let game = Arc::clone(&global_game);

self.socket = Some(
    SocketBuilder::new(self.host.clone())
        .on("connect", |_,_|{})    
        .on("REBOOT", move | _,_| {
            let game_clone = Arc::clone(&game);
            // 调度到主线程执行
            let _ = queue_microtask(move || {
                let mut game = game_clone.lock().unwrap();
                game.Reiniciar();
            });
        })
        .on("message", callback) 
        .on("error", |err, _| eprintln!("Error: {:#?}", err))        
        .connect()        
        .expect("Connection failed")
);

注意事项

  • WASM环境下所有DOM操作必须在主线程执行,即使方案1用了Arc,实际执行时也必须确保在主线程调用Reiniciar,否则会触发浏览器API错误。
  • 如果Game实例是全局唯一的,也可以用lazy_static创建全局的Arc<Mutex<Game>>,在回调中直接获取调用。

内容的提问来源于stack exchange,提问作者Felipe Mateus

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.26 20:12:06