Rust/Wasm_bindgen并发调用send函数遇unsafe aliasing错误如何解决?
我正在开发首个Rust/wasm_bindgen项目,使用web_sys向其他服务发起请求。我有多个如下所示的控制器,它们从JS接收请求参数后调用send函数发起请求:
pub async fn get_some_data(&mut self, req_param: String) -> Result<JsValue, JsValue> { let req_param = String::from(req_param); let request_endpoint = "some_endpoint"; let request_body = json!({ "req_param": req_param }); let json = self.send(request_endpoint.to_string(), request_body.to_string()).await?; Ok(json) }
以下是send函数的实现:
pub async fn send(&mut self, request_endpoint: String, request_body: String) -> Result<JsValue, JsValue> { let window = window().ok_or_else(|| JsValue::from_str("Failed to get window object"))?; // prepare params ... let mut req = RequestInit::new(); req.method("POST"); req.mode(RequestMode::Cors); req.body(Some(&JsValue::from_str(&request_body.to_string()))); let request = Request::new_with_str_and_init(&url, &req)?; request.headers().set("Content-type", "application/json")?; request.headers().set("Accept", "text/plain")?; let resp_value = JsFuture::from(window.fetch_with_request(&request)).await?; let response: Response = resp_value.dyn_into()?; // check API response if !response.ok() { return Err(JsValue::from_str(&format!("HTTP error response: {}",response.status()))); } // parse API response as JSON and return let json = JsFuture::from(response.json()?) .await?; Ok(json) }
随着控制器数量增加,我遇到了错误:recursive use of an object detected which would lead to unsafe aliasing in rust
我认为该错误是由于send函数被并发调用导致的——如果在JS中用setTimeout延迟其中一个控制器的调用,错误就会消失。请问如何让send函数能够处理并发请求?
这个错误的核心原因是:send函数声明了&mut self,但并发调用时多个异步任务会同时持有对self的可变引用,这违反了Rust的可变引用独占规则,wasm_bindgen检测到这种不安全的别名情况后抛出了错误。根据你的实际需求,可以用以下两种方式解决:
场景1:send函数无需修改self状态
如果send函数里并没有实际修改self的成员变量(比如你的示例代码里,send只是发起请求,没有操作self的状态),直接把&mut self改成&self即可:
pub async fn send(&self, request_endpoint: String, request_body: String) -> Result<JsValue, JsValue> { // 原代码逻辑完全不变 }
Rust允许同时存在多个不可变引用,这样多个并发请求就能正常执行了。
场景2:send函数需要修改self状态
如果send必须修改self的状态(比如维护请求计数器、共享配置等),需要用线程安全的同步原语保护共享状态。在wasm环境中,可以使用Arc<Mutex<...>>来包裹需要修改的成员:
- 重构结构体,用Arc+Mutex包装可变状态:
use std::sync::{Arc, Mutex}; use wasm_bindgen::prelude::*; #[wasm_bindgen] pub struct ApiController { // 示例:用Arc+Mutex包裹需要修改的请求计数器 request_count: Arc<Mutex<u32>>, // 其他不需要修改的成员可以保留原样 } #[wasm_bindgen] impl ApiController { #[wasm_bindgen(constructor)] pub fn new() -> Self { ApiController { request_count: Arc::new(Mutex::new(0)), } } }
- 修改send函数,使用
&self并通过lock获取可变访问权:
pub async fn send(&self, request_endpoint: String, request_body: String) -> Result<JsValue, JsValue> { // 锁定计数器并修改状态,注意操作完成后提前释放锁 let mut count = self.request_count.lock().map_err(|_| { JsValue::from_str("Failed to lock request counter") })?; *count += 1; drop(count); // 提前释放锁,避免await期间阻塞其他请求 // 原请求逻辑不变 let window = window().ok_or_else(|| JsValue::from_str("Failed to get window object"))?; let mut req = RequestInit::new(); req.method("POST"); req.mode(RequestMode::Cors); req.body(Some(&JsValue::from_str(&request_body))); // 补充示例URL拼接(原代码中url变量未定义) let url = format!("https://your-api-domain/{}", request_endpoint); let request = Request::new_with_str_and_init(&url, &req)?; request.headers().set("Content-type", "application/json")?; request.headers().set("Accept", "text/plain")?; let resp_value = JsFuture::from(window.fetch_with_request(&request)).await?; let response: Response = resp_value.dyn_into()?; if !response.ok() { return Err(JsValue::from_str(&format!("HTTP error response: {}", response.status()))); } let json = JsFuture::from(response.json()?).await?; Ok(json) }
关键提醒:一定要在await调用前释放Mutex锁(比如用drop(count)),否则其他并发请求会被阻塞,失去并发处理的意义。
内容的提问来源于stack exchange,提问作者Pypax

