Js binding for large rust object using wasm-bindgen - rust

I want to write a vscode extension that displays the content of a large binary file, written with bincode:
#[macro_use]
extern crate serde_derive;
use std::collections::HashMap;
use std::fs::File;
use std::io::{BufReader, BufWriter};
#[derive(Serialize, Deserialize)]
pub struct MyValue {
pub name: String,
}
#[derive(Serialize, Deserialize)]
pub struct MyStruct {
pub data: HashMap<String, MyValue>,
}
impl MyStruct {
pub fn dump(&self, filename: &str) -> Result<(), String> {
let file = File::create(filename).map_err(|msg| msg.to_string())?;
let writer = BufWriter::new(file);
bincode::serialize_into(writer, self).map_err(|msg| msg.to_string())
}
pub fn load(filename: &str) -> Result<Self, String> {
let file = File::open(filename).map_err(|msg| msg.to_string())?;
let reader = BufReader::new(file);
bincode::deserialize_from::<BufReader<_>, Self>(reader).map_err(|msg| msg.to_string())
}
}
Therefore there is a wasm binding:
#[wasm_bindgen]
#[derive(Clone)]
pub struct PyMyStruct {
inner: Arc<MyStruct>,
}
#[wasm_bindgen]
impl PyMyStruct {
pub fn new(filename: &str) -> Self {
Self {
inner: Arc::new(MyStruct::load(filename).unwrap()),
}
}
pub fn header(self) -> Array {
let keys = Array::new();
for key in self.inner.data.keys() {
keys.push(&JsValue::from_str(key));
}
keys
}
pub fn value(&self, name: &str) -> JsValue {
if let Some(value) = self.inner.data.get(name) {
JsValue::from_serde(value).unwrap_or(JsValue::NULL)
} else {
JsValue::NULL
}
}
}
which provides a simple interface to the JavaScript world in order to access the content of that file.
Using Arc in order to prevent expensive unintended memory copy when handling on the JavaScript side.
(It might look strange that keys is not marked as mutable but the rust compiler recomended that way)
When running the test code:
const {PyMyStruct} = require("./corejs.js");
let obj = new PyMyStruct("../../dump.spb")
console.log(obj.header())
you get the error message:
Error: null pointer passed to rust
Does someone know how to handle this use case?
Thank you!

The issue here is that you are using new PyMyStruct() instead of PyMyStruct.new(). In wasm-bindgen's debug mode you will get an error about this at runtime. Using .new() will fix your issue:
let obj = PyMyStruct.new("../../dump.spb")
If you add the #[wasm_bindgen(constructor)] annotation to the new method, then new PyMyStruct() will work as well:
#[wasm_bindgen]
impl PyMyStruct {
#[wasm_bindgen(constructor)]
pub fn new(filename: &str) -> Self {
Self {
inner: 1,
}
}
}
Now this is fine:
let obj = new PyMyStruct("../../dump.spb")

I solved that problem by using https://neon-bindings.com instead of compiling the API to web-assembly.
The binding here looks as follow:
use core;
use std::rc::Rc;
use neon::prelude::*;
#[derive(Clone)]
pub struct MyStruct {
inner: Rc<core::MyStruct>,
}
declare_types! {
pub class JsMyStruct for MyStruct {
init(mut cx) {
let filename = cx.argument::<JsString>(0)?.value();
match core::MyStruct::load(&filename) {
Ok(inner) => return Ok(MyStruct{
inner: Rc::new(inner)
}),
Err(msg) => {
panic!("{}", msg)
}
}
}
method header(mut cx) {
let this = cx.this();
let container = {
let guard = cx.lock();
let this = this.borrow(&guard);
(*this).clone()
};
let keys = container.inner.data.keys().collect::<Vec<_>>();
let js_array = JsArray::new(&mut cx, keys.len() as u32);
for (i, obj) in keys.into_iter().enumerate() {
let js_string = cx.string(obj);
js_array.set(&mut cx, i as u32, js_string).unwrap();
}
Ok(js_array.upcast())
}
method value(mut cx) {
let key = cx.argument::<JsString>(0)?.value();
let this = cx.this();
let container = {
let guard = cx.lock();
let this = this.borrow(&guard);
(*this).clone()
};
if let Some(data) = container.inner.data.get(&key) {
return Ok(neon_serde::to_value(&mut cx, data)?);
} else {
panic!("No value for key \"{}\" available", key);
}
}
}
}
register_module!(mut m, {
m.export_class::<JsMyStruct>("MyStruct")?;
Ok(())
});

Related

How to create factory that dynamically creates values and returns borrows to them?

I'd like to have a struct called Factory that dynamically produces new Strings, keeps them inside itself and returns &str borrows of them that live as long as the Factory value itself.
I tried to keep new values inside in a Vec but as Vec grows borrows to elements would get invalidated so they don't live long enough. I tried wrapping them in Boxes, RefCells but I encounter same problems.
I would also like to call this factory method inside a loop so I can make new String in each iteration and get a borrow of it to keep somewhere.
There's a crate called string-interner: https://docs.rs/string-interner/latest/string_interner/index.html
It might be a good idea to use it either directly or through similar structs as below if what you are after are just String handles.
That's what I've got so far thanks to your comments:
use std::{ cell::{Ref, RefCell}, rc::Rc, };
struct StringHandle {
key: usize,
store: Rc<RefCell<Vec<String>>>,
}
impl StringHandle {
pub fn get(&self) -> Ref<String> {
Ref::map(self.store.borrow(), |v| &v[self.key])
}
}
struct Factory {
pub store: Rc<RefCell<Vec<String>>>,
}
impl Factory {
pub fn make_next_string(&mut self) -> StringHandle {
let len = self.store.borrow().len();
self.store.borrow_mut().push(format!("string no. {}", len));
StringHandle {
store: self.store.clone(),
key: len,
}
}
pub fn new() -> Factory {
Factory { store: Rc::new(RefCell::new(vec![])) }
}
}
let mut f = Factory::new();
let mut strs: Vec<StringHandle> = vec![];
for _ in 0..5 {
let handle = f.make_next_string();
strs.push(handle);
}
for handle in strs {
println!("{}", handle.get());
}
And generic version for structs other than String:
use std::{ cell::{Ref, RefCell, RefMut}, rc::Rc, };
struct Handle<T> {
key: usize,
store: Rc<RefCell<Vec<T>>>,
}
impl<T> Handle<T> {
pub fn get(&self) -> Ref<T> {
Ref::map(self.store.borrow(), |v| &v[self.key])
}
pub fn get_mut(&self) -> RefMut<T> {
RefMut::map(self.store.borrow_mut(), |v| &mut v[self.key])
}
}
struct Factory<T> {
pub store: Rc<RefCell<Vec<T>>>,
}
impl<T: Default> Factory<T> {
pub fn make_next(&mut self) -> Handle<T> {
let len = self.store.borrow().len();
self.store.borrow_mut().push(T::default());
Handle {
store: self.store.clone(),
key: len,
}
}
pub fn new() -> Factory<T> {
Factory { store: Rc::new(RefCell::new(vec![])) }
}
}
#[derive(Debug)]
struct Data {
pub number: i32
}
impl Default for Data {
fn default() -> Self {
Data { number: 0 }
}
}
let mut objs: Vec<Handle<Data>> = vec![];
let mut f: Factory<Data> = Factory::new();
for i in 0..5 {
let handle = f.make_next();
handle.get_mut().number = i;
objs.push(handle);
}
for handle in objs {
println!("{:?}", handle.get());
}
First, if you have a &mut access to the interner, you don't need RefCell on it. But you likely want to access it through shared references so you do need.
Another way is to return a newtyped index into the Vec instead of references. This saves the indirection, but requires an access to the interner to access the interned string, so it may not fulfill the requirements. This also does not allow you to allocate new strings while you keep references to the old around (using RefCell will not help, it will just panic):
use std::ops::Index;
struct StringHandle(usize);
struct Factory {
pub store: Vec<String>,
}
impl Factory {
pub fn make_next_string(&mut self) -> StringHandle {
let len = self.store.len();
self.store.push(format!("string no. {}", len));
StringHandle(len)
}
pub fn new() -> Factory {
Factory { store: vec![] }
}
}
impl Index<StringHandle> for Factory {
type Output = str;
fn index(&self, index: StringHandle) -> &Self::Output {
&self.store[index.0]
}
}
fn main() {
let mut f = Factory::new();
let mut strs: Vec<StringHandle> = vec![];
for _ in 0..5 {
let handle = f.make_next_string();
strs.push(handle);
}
for handle in strs {
println!("{}", &f[handle]);
}
}
The best way is to use an arena. It allows you to yield references (and therefore does not require access to the interner to access interned strings), and keep the old around while making new. The disadvantages are that it requires using a crate, as you probably don't want to implement the arena yourself (this also requires unsafe code), and that you can't store that interner alongside the interned strings (this is a self-referential struct). You can use the typed-arena crate for that:
use std::cell::Cell;
use typed_arena::Arena;
struct Factory {
store: Arena<String>,
len: Cell<u32>,
}
impl Factory {
pub fn make_next_string(&self) -> &str {
let len = self.len.get();
self.len.set(len + 1);
self.store.alloc(format!("string no. {}", len))
}
pub fn new() -> Factory {
Factory { store: Arena::new(), len: Cell::new(0) }
}
}
fn main() {
let f = Factory::new();
let mut strs: Vec<&str> = vec![];
for _ in 0..5 {
let interned = f.make_next_string();
strs.push(interned);
}
for interned in strs {
println!("{}", interned);
}
}
You can also store strs inside the arean (instead of Strings) The advantages are better cache access as the structure is more flat and much faster drop of the interner itself due to not needing to loop over and drop the stored strings; the disadvantage is that you need to copy the strings before you store them. I recommend bumpalo:
use std::cell::Cell;
use bumpalo::Bump;
struct Factory {
store: Bump,
len: Cell<u32>,
}
impl Factory {
pub fn make_next_string(&self) -> &str {
let len = self.len.get();
self.len.set(len + 1);
self.store.alloc_str(&format!("string no. {}", len))
}
pub fn new() -> Factory {
Factory { store: Bump::new(), len: Cell::new(0) }
}
}
fn main() {
let f = Factory::new();
let mut strs: Vec<&str> = vec![];
for _ in 0..5 {
let interned = f.make_next_string();
strs.push(interned);
}
for interned in strs {
println!("{}", interned);
}
}

"can't capture dynamic environment in a fn item use the `|| { ... }` closure form instead " while trying to parse cli arguments into a json file

I am facing an error that I have failed to understand properly, maybe I have insufficient knowledge on closure and there proper use. The error is can't capture dynamic environment in a fn item use the `|| { ... }` closure form instead I have defined closures for the handle_app_add_new_app in the AppParams impl as well. All code has been shared below.
main.rs
use std::collections::HashMap;
use std::fs::OpenOptions;
use std::{str, io::Write as write};
use std::env;
mod utils;
use utils::*;
fn main() {
let args: Vec<String> = env::args().collect();
let chain_id = args[0];
let chain_address = args[1];
let appname = args[2];
let abi_endpoint = args[3];
let app_address = args[4];
let token_name = args[5];
let token_address = args[6];
let new_token = &TokenParams::new(&token_name, &token_address);
let mut chain_tokens = HashMap::new();
// Add new tokens to chain.
chain_tokens.insert(
new_token.token_name,
new_token.token_address,
);
let app = AppParams::new(
&appname, &abi_endpoint, &app_address, &chain_tokens
);
pub unsafe fn convert_to_u8<T: Sized>(p: &T) -> &[u8] {
::std::slice::from_raw_parts(
(p as *const T) as *const u8,
::std::mem::size_of::<T>(),
)
}
//add new application(dex,payments...etc)
pub async fn handle_add_new_app(chain_id: &str, chain_address: &str,) -> Box<dyn Fn(ChainParams) -> ChainParams>{
let new_app= Box::new(|x| ChainParams::new(
&chain_id,
&chain_address,
app,
));
let bytes: &[u8] = unsafe {convert_to_u8(&new_app) };
let mut updated_file = OpenOptions::new().append(true).open("config.json").expect("Unable to open file");
updated_file.write_all(bytes).expect("adding app to config failed");
new_app
}
}
The utils file
use std::{error::Error, collections::HashMap};
use serde_json::Value;
#[derive(serde::Deserialize)]
pub struct Addresses{
pub to_address: String,
pub from_address: String,
}
impl Addresses{}
#[derive(serde::Deserialize)]
pub struct TokenParams {
pub token_name: String,
pub token_address: String,
}
impl TokenParams{
/// create a new token instance
pub fn new(token_name: &str, token_address: &str) -> Self {
let token_name = token_name;
let token_address = token_address;
TokenParams {
token_address: token_address.to_string(),
token_name: token_name.to_string(),
}
}
pub fn get_token(chain: &str, app: &str) -> Result<Vec<Value>, Box<dyn Error>>{
let token_getter = {
// Load the first file into a string.
let config = std::fs::read_to_string("Config.json").unwrap();
// Parse the string into a dynamically-typed JSON structure.
serde_json::from_str::<Value>(&config).unwrap()
};
// Get the get tokens with chain and app(eg dex)
let chain_elements = token_getter[chain][app].as_array().unwrap();
Ok(chain_elements.to_vec())
}
}
#[derive(serde::Deserialize)]
pub struct AppParams {
pub appname: String,
pub abi_endpoint: String,
pub app_address: String,
#[serde(flatten)]
pub tokens: HashMap<String, String>
}
impl AppParams{
/// create a new App(dex, marketplace...etc) instance
pub fn new(appname: &str, abi_endpoint: &str, address: &str, tokens: &HashMap<String, String>) -> Box<dyn Fn(AppParams) -> AppParams> {
let appname = appname;
let abi_endpoint = abi_endpoint;
let address = address;
let mut app_tokens = HashMap::new();
Box::new(|x| AppParams {
appname: appname.to_string(),
abi_endpoint: abi_endpoint.to_string(),
tokens: app_tokens,
app_address: address.to_string(),
})
}
pub fn get_app_params(self, chain: &str) -> Result<Vec<Value>, Box<dyn Error>>{
let app_getter = {
// Load the first file into a string.
let config = std::fs::read_to_string("Config.json").unwrap();
// Parse the string into a dynamically-typed JSON structure.
serde_json::from_str::<Value>(&config).unwrap()
};
// Get the get tokens with chain and app(eg dex)
let chain_elements = app_getter[chain][self.appname].as_array().unwrap();
Ok(chain_elements.to_vec())
}
}
#[derive(serde::Deserialize)]
pub struct ChainParams {
pub chain_id: String,
pub chain_address: String,
#[serde(flatten)]
pub chain_application: Vec<AppParams>,
}
impl ChainParams{
/// get an App(dex, marketplace...etc) instance
pub fn new(chain_id: &str, chain_address: &str, chain_application: AppParams ) -> Self {
let chain_id = chain_id;
let chain_address = chain_address;
let new_tokens = HashMap::new();
let new_application = AppParams::new(
&chain_application.appname,
&chain_application.abi_endpoint,
&chain_application.app_address,
&new_tokens,
);
ChainParams {
chain_id: chain_id.to_string(),
chain_address: chain_address.to_string(),
chain_application: vec![new_application(chain_application)],
}
}
//get chain params
pub fn get_chain_params(self) -> Result<Vec<Value>, Box<dyn Error>>{
let app_getter = {
// Load the first file into a string.
let config = std::fs::read_to_string("Config.json").unwrap();
// Parse the string into a dynamically-typed JSON structure.
serde_json::from_str::<Value>(&config).unwrap()
};
// Get the get tokens with chain and app(eg dex)
let chain_elements = app_getter[self.chain_id].as_array().unwrap();
Ok(chain_elements.to_vec())
}
}
A detailed explanation of your answer will be highly appreciated.
Minimized example, produced by removing everything irrelevant to the error:
fn main() {
let app = ();
pub fn captures_app() {
let _ = app;
}
}
Playground
The problem here is the fact that the functions are just functions, they doesn't hold any state. Therefore, they can't refer to anything other then their arguments and static variables, even if they're placed inside another function; the placement governs only the name scoping, i.e. "where this identifier will refer to this function", not the access to the environment.
To see why this is a problem, consider this:
fn returns_fn() -> fn() {
let app = String::from("app");
pub fn captures_app() {
println!("{}", app);
}
captures_app
}
Here, fn() is a function pointer. Every function can be coerced to the function pointer, therefore it must be callable by using the pointer only - but it depends on the app string, which can very well be different for every returns_fn's call and therefore can't be integrated into the returned pointer.
Now, what to do about it? There are two possible courses of action.
You can pass everything the function needs via its arguments. That is, the first example would become:
fn main() {
let app = ();
pub fn captures_app(app: ()) {
let _ = app;
}
}
and your own code will look as something like this:
pub async fn handle_add_new_app(
chain_id: &str,
chain_address: &str,
app: <return type of AppParams::new>
) -> Box<dyn Fn(ChainParams) -> ChainParams> {
// implementation
}
Or you can make the function a closure instead. In minimized example, it'll be like this:
fn main() {
let app = ();
let captures_app = || {
let _ = app;
}
}
In your code, that's slightly more tricky, because async closures are unstable yet, but you can do it like this:
let handle_add_new_app = move |chain_id: &str, chain_address: &str| -> Box<dyn Fn(ChainParams) -> ChainParams> {
async move {
// implementation
}
}

How to create a single threaded singleton in Rust?

I'm currently trying to wrap a C library in rust that has a few requirements. The C library can only be run on a single thread, and can only be initialized / cleaned up once on the same thread. I want something something like the following.
extern "C" {
fn init_lib() -> *mut c_void;
fn cleanup_lib(ctx: *mut c_void);
}
// This line doesn't work.
static mut CTX: Option<(ThreadId, Rc<Context>)> = None;
struct Context(*mut c_void);
impl Context {
fn acquire() -> Result<Rc<Context>, Error> {
// If CTX has a reference on the current thread, clone and return it.
// Otherwise initialize the library and set CTX.
}
}
impl Drop for Context {
fn drop(&mut self) {
unsafe { cleanup_lib(self.0); }
}
}
Anyone have a good way to achieve something like this? Every solution I try to come up with involves creating a Mutex / Arc and making the Context type Send and Sync which I don't want as I want it to remain single threaded.
A working solution I came up with was to just implement the reference counting myself, removing the need for Rc entirely.
#![feature(once_cell)]
use std::{error::Error, ffi::c_void, fmt, lazy::SyncLazy, sync::Mutex, thread::ThreadId};
extern "C" {
fn init_lib() -> *mut c_void;
fn cleanup_lib(ctx: *mut c_void);
}
#[derive(Debug)]
pub enum ContextError {
InitOnOtherThread,
}
impl fmt::Display for ContextError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
ContextError::InitOnOtherThread => {
write!(f, "Context already initialized on a different thread")
}
}
}
}
impl Error for ContextError {}
struct StaticPtr(*mut c_void);
unsafe impl Send for StaticPtr {}
static CTX: SyncLazy<Mutex<Option<(ThreadId, usize, StaticPtr)>>> =
SyncLazy::new(|| Mutex::new(None));
pub struct Context(*mut c_void);
impl Context {
pub fn acquire() -> Result<Context, ContextError> {
let mut ctx = CTX.lock().unwrap();
if let Some((id, ref_count, ptr)) = ctx.as_mut() {
if *id == std::thread::current().id() {
*ref_count += 1;
return Ok(Context(ptr.0));
}
Err(ContextError::InitOnOtherThread)
} else {
let ptr = unsafe { init_lib() };
*ctx = Some((std::thread::current().id(), 1, StaticPtr(ptr)));
Ok(Context(ptr))
}
}
}
impl Drop for Context {
fn drop(&mut self) {
let mut ctx = CTX.lock().unwrap();
let (_, ref_count, ptr) = ctx.as_mut().unwrap();
*ref_count -= 1;
if *ref_count == 0 {
unsafe {
cleanup_lib(ptr.0);
}
*ctx = None;
}
}
}
I think the most 'rustic' way to do this is with std::sync::mpsc::sync_channel and an enum describing library operations.
The only public-facing elements of this module are launch_lib(), the SafeLibRef struct (but not its internals), and the pub fn that are part of the impl SafeLibRef.
Also, this example strongly represents the philosophy that the best way to deal with global state is to not have any.
I have played fast and loose with the Result::unwrap() calls. It would be more responsible to handle error conditions better.
use std::sync::{ atomic::{ AtomicBool, Ordering }, mpsc::{ SyncSender, Receiver, sync_channel } };
use std::ffi::c_void;
extern "C" {
fn init_lib() -> *mut c_void;
fn do_op_1(ctx: *mut c_void, a: u16, b: u32, c: u64) -> f64;
fn do_op_2(ctx: *mut c_void, a: f64) -> bool;
fn cleanup_lib(ctx: *mut c_void);
}
enum LibOperation {
Op1(u16,u32,u64,SyncSender<f64>),
Op2(f64, SyncSender<bool>),
Terminate(SyncSender<()>),
}
#[derive(Clone)]
pub struct SafeLibRef(SyncSender<LibOperation>);
fn lib_thread(rx: Receiver<LibOperation>) {
static LIB_INITIALIZED: AtomicBool = AtomicBool::new(false);
if LIB_INITIALIZED.compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst).is_err() {
panic!("Tried to double-initialize library!");
}
let libptr = unsafe { init_lib() };
loop {
let op = rx.recv();
if op.is_err() {
unsafe { cleanup_lib(libptr) };
break;
}
match op.unwrap() {
LibOperation::Op1(a,b,c,tx_res) => {
let res: f64 = unsafe { do_op_1(libptr, a, b, c) };
tx_res.send(res).unwrap();
},
LibOperation::Op2(a, tx_res) => {
let res: bool = unsafe { do_op_2(libptr, a) };
tx_res.send(res).unwrap();
}
LibOperation::Terminate(tx_res) => {
unsafe { cleanup_lib(libptr) };
tx_res.send(()).unwrap();
break;
}
}
}
}
/// This needs to be called no more than once.
/// The resulting SafeLibRef can be cloned and passed around.
pub fn launch_lib() -> SafeLibRef {
let (tx,rx) = sync_channel(0);
std::thread::spawn(|| lib_thread(rx));
SafeLibRef(tx)
}
// This is the interface that most of your code will use
impl SafeLibRef {
pub fn op_1(&self, a: u16, b: u32, c: u64) -> f64 {
let (res_tx, res_rx) = sync_channel(1);
self.0.send(LibOperation::Op1(a, b, c, res_tx)).unwrap();
res_rx.recv().unwrap()
}
pub fn op_2(&self, a: f64) -> bool {
let (res_tx, res_rx) = sync_channel(1);
self.0.send(LibOperation::Op2(a, res_tx)).unwrap();
res_rx.recv().unwrap()
}
pub fn terminate(&self) {
let (res_tx, res_rx) = sync_channel(1);
self.0.send(LibOperation::Terminate(res_tx)).unwrap();
res_rx.recv().unwrap();
}
}

Sending a closure (which returns a struct with a trait) to a thread leads to sized error

I'm trying to send a closure which will generate a structure to a thread, however when I try to do it I get a Sized error. I understand the error (the size is indeed not known at compile time), however adding Boxes and other such tricks does not seem to solve it.
I've tried to look into how to implement the Sized trait, however it seems to be quite special and honestly above my understanding.
I've written a minimal reproducible example:
use std::thread;
trait DataProcess {
fn start(&self);
fn run(&self);
fn stop(&self);
}
struct SomeDP {
name: String,
}
impl DataProcess for SomeDP {
fn start(&self) {
println!("Started");
}
fn run(&self) {
println!("Running");
}
fn stop(&self) {
println!("Stopped");
}
}
fn thread_maker(builder: Box<dyn Fn() -> (dyn DataProcess + Send)>) {
let thread_builder = thread::Builder::new();
let handle = thread_builder.spawn(move || {
let dp = builder();
dp.start();
});
}
fn main() {
let dp_builder = || SomeDP {
name: "nice".to_string(),
};
thread_maker(Box::new(dp_builder));
}
Which you can also find on the playground here
This works
use std::thread;
trait DataProcess{
fn start(&self);
fn run(&self);
fn stop(&self);
}
struct SomeDP{
name: String
}
impl DataProcess for SomeDP{
fn start(&self){println!("Started");}
fn run(&self){println!("Running");}
fn stop(&self){println!("Stopped");}
}
fn thread_maker<F>(builder: F)
where
F: Fn() -> Box<dyn DataProcess>,
F: Send + 'static {
let thread_builder = thread::Builder::new();
let handle = thread_builder.spawn(
move ||{
let dp = builder();
dp.start();
}
);
}
fn main(){
let dp_builder = || -> Box<dyn DataProcess> {
Box::new(SomeDP{name: "nice".to_string()})
};
thread_maker(dp_builder);
}

Rust panic::catch_unwind no use in WebAssembly

I try to use panic::catch_unwind to catch some errors, but it seems no use, and there is an example:
rust:
use std::{sync::Mutex};
use wasm_bindgen::prelude::*;
use std::sync::PoisonError;
use std::panic;
pub struct CurrentStatus {
pub index: i32,
}
#[wasm_bindgen]
extern {
pub fn alert(s: &str);
}
impl CurrentStatus {
fn new() -> Self {
CurrentStatus {
index: 1,
}
}
fn get_index(&mut self) -> i32 {
self.index += 1;
self.index.clone()
}
fn add_index(&mut self) {
self.index += 2;
}
}
lazy_static! {
pub static ref FOO: Mutex<CurrentStatus> = Mutex::new(CurrentStatus::new());
}
unsafe impl Send for CurrentStatus {}
#[wasm_bindgen]
pub fn add_index() {
FOO.lock().unwrap_or_else(PoisonError::into_inner).add_index();
}
#[wasm_bindgen]
pub fn get_index() -> i32 {
let mut foo = FOO.lock().unwrap_or_else(PoisonError::into_inner);
let result = panic::catch_unwind(|| {
panic!("error happen!"); // original panic! code
});
if result.is_err() {
alert("panic!!!!!panic");
}
return foo.get_index();
}
js:
const js = import("../pkg/hello_wasm.js");
js.then(js => {
window.js = js;
console.log(js.get_index());
js.add_index();
});
I think it should catch the panic and I can call add_index then.
But In fact I can call neither after the panic.
I wish I can catch the panic from one function so when the users call other functions just all right..
Thanks very much

Resources