Wasm-bindgen调用Rust函数时触发RuntimeError: unreachable executed错误求助
最近在做一个Web项目,用Rust编译WASM实现Abrams-Strogatz种群模型的求解,前端通过wasm-bindgen调用Rust导出的函数,但运行时一直碰到Uncaught (in promise) RuntimeError: unreachable executed错误,卡了好几天,来求大家帮忙排查!
我的代码结构
前端调用代码
import init, { solve_abrams_strogatz } from './pkg/langshift.js'; async function run() { await init(); console.log("Loaded"); // 这行能正常打印 Plotly.newPlot('gd', { "data": [{ "y": solve_abrams_strogatz(0.5, 0.5, 1.0, 2.0, 0.5, 100.0) // 错误触发在这里 }], "layout": { "xaxis": { "title": "Time" }, "yaxis": { "title": "Population" }, "title": "Abrams Strogatz Model", "width": 800, "height": 600 } }); console.log("Done"); // 这行永远到不了 } run();
Rust根模块(lib.rs)
pub mod abrams_strogatz; use wasm_bindgen::prelude::*; #[wasm_bindgen] pub fn solve_abrams_strogatz(x: f64, y: f64, c: f64, a: f64, s: f64, t: f64) -> Vec<f64> { abrams_strogatz::Community { x, y, c, a, s } .solve(t) }
Abrams-Strogatz实现模块
use differential_equations::prelude::*; #[derive(Copy, Clone)] pub struct Community { pub x: f64, pub y: f64, pub c: f64, pub a: f64, pub s: f64, } impl ODE for Community { fn diff(&self, _t: f64, y: &f64, dydt: &mut f64) { *dydt = (1.0 - y) * self.c * y.powf(self.a) * self.s - y * self.c * (1.0 - y).powf(self.a) * (1.0 - self.s); } } impl Community { pub fn n(&self) -> u32 { (self.x + self.y) as u32 } pub fn solve(&self, t: f64) -> Vec<f64> { let problem = ODEProblem::new( *self, 0.0, t, self.y / (self.n() as f64), ); let mut solver = ExplicitRungeKutta::dop853() .rtol(1e-8) .atol(1e-6); match problem.solve(&mut solver) { Ok(y) => y, Err(e) => panic!("{}", e), }.iter().map(|(_, y)| *y).collect() } }
编译方式
我用wasm-pack build --target web生成WASM库,依赖的differential-equations和nalgebra crate官方说明都支持WASM编译,但就是跑不起来,错误指向调用solve_abrams_strogatz的时候触发了unreachable指令。
问题排查与解决方案
核心原因:WASM环境下的Panic表现
首先,WASM里的unreachable executed几乎都是Rust代码触发了panic导致的——因为Rust默认的panic处理在WASM环境中会生成unreachable指令,而不是像原生环境那样打印堆栈。你代码里的solve方法用了panic!,大概率是微分方程求解器返回了错误,触发了panic。
步骤1:先拿到具体错误信息
我们可以把函数返回类型改成Result,把panic换成返回错误,这样就能在前端看到具体问题:
修改Rust代码
首先修改abrams_strogatz模块的solve方法,返回Result而不是panic:
pub fn solve(&self, t: f64) -> Result<Vec<f64>, String> { let problem = ODEProblem::new( *self, 0.0, t, self.y / (self.n() as f64), ); let mut solver = ExplicitRungeKutta::dop853() .rtol(1e-8) .atol(1e-6); match problem.solve(&mut solver) { Ok(y) => Ok(y.iter().map(|(_, y)| *y).collect()), Err(e) => Err(format!("求解器错误: {}", e)), } }
然后修改lib.rs的导出函数,适配Result并转换成JS能处理的类型:
#[wasm_bindgen] pub fn solve_abrams_strogatz(x: f64, y: f64, c: f64, a: f64, s: f64, t: f64) -> Result<Vec<f64>, JsValue> { abrams_strogatz::Community { x, y, c, a, s } .solve(t) .map_err(|e| JsValue::from_str(&e)) }
修改前端代码捕获错误
async function run() { await init(); console.log("Loaded"); try { const yData = await solve_abrams_strogatz(0.5, 0.5, 1.0, 2.0, 0.5, 100.0); Plotly.newPlot('gd', { "data": [{ "y": yData }], "layout": { "xaxis": { "title": "Time" }, "yaxis": { "title": "Population" }, "title": "Abrams Strogatz Model", "width": 800, "height": 600 } }); console.log("Done"); } catch (err) { console.error("发生错误:", err); } }
现在运行就能在控制台看到具体的错误原因了,比如求解器不收敛、初始值非法等。
步骤2:检查初始值计算的潜在问题
看你的初始值是self.y / (self.n() as f64),而self.n()是把x+y转成u32。如果x+y是小于1的浮点数(比如0.999999),转成u32会变成0,导致除以0触发panic。建议把n()改成返回浮点数:
pub fn n(&self) -> f64 { self.x + self.y } // 初始值改成self.y / self.n()
步骤3:排查求解器的WASM兼容性
有些高阶求解器(比如dop853)可能用到了WASM不支持的指令,或者依赖的底层库有问题。可以先换成简单的求解器测试,比如RK4:
let mut solver = ExplicitRungeKutta::rk4() .rtol(1e-8) .atol(1e-6);
步骤4:启用panic钩子,直接查看panic详情
如果还是想保留panic的方式,或者需要看堆栈信息,可以添加console_error_panic_hook crate,把Rust的panic信息输出到浏览器控制台:
Cargo.toml添加依赖
[dependencies] console_error_panic_hook = "0.1.7"
在lib.rs添加初始化钩子
#[wasm_bindgen(start)] pub fn init_panic_hook() { console_error_panic_hook::set_once(); }
这样即使触发panic,也能在浏览器控制台看到具体的panic信息和堆栈,不用再猜原因了。
内容来源于stack exchange

