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不会带来真正的线程竞争,只是用来满足类型约束。
修改步骤:
- 先为Game实现
Clonetrait(web_sys下的类型大多已实现Clone,直接派生即可):
#[derive(Clone)] struct Game { // ... 原有字段 }
- 在
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
相关产品推荐
相关产品推荐

