Reputation: 320
I have two structs exported to Javascript. I can create instances and use them in JavaScript without any error but when I push the instances into a vector in Rust side, I have an error "Uncaught Error: null pointer passed to rust"
Since ownership is changed, it is totally normal that JS objects turns null but I also need to keep my JavaScript objects in order to change things in JavaScript side.
Is there any correct way to keep "vect" object not null and open to changes?
I added a working example. You can see error in your browser's console.
Rust code
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
struct Vector3 {
x: f32,
y: f32,
z: f32,
}
#[wasm_bindgen]
impl Vector3 {
#[wasm_bindgen(constructor)]
pub fn new() -> Vector3 {
return Vector3 {
x: 0.0,
y: 0.0,
z: 0.0,
};
}
pub fn get_x(&self) -> f32 {
self.x
}
}
#[wasm_bindgen(extends = Object)]
struct Group {
list: Vec<Vector3>,
}
#[wasm_bindgen]
impl Group {
#[wasm_bindgen(constructor)]
pub fn new() -> Group {
return Group { list: vec![] };
}
pub fn add(&mut self, vec: Vector3) {
self.list.push(vec);
}
}
JavaScript code
let group = new Group();
let list = [];
for (let i = 0; i < 10; i++) {
let vect = new Vector3();
list.push(vect);
group.add(vect);
}
setInterval(() => {
for (let i = 0; i < list.length; i++) {
const vect = list[i];
console.log(vect.get_x());
}
}, 1000);
Upvotes: 5
Views: 2002
Reputation: 411
You should be extremely wary of having your data duplicated - one copy is on the JS side and then on the WASM side in Rust. The problem here is that the Group.add
moves the value out so that once you called group.add, the vector internal "heap pointer" (maintained by the interop code generated by wasm-bindgen) changes and the previous copy becomes invalid, so the values inserted into the list
are pretty much useless.
The Rust-y way of dealing with this situation would be to keep a list of borrowed values instead, and manage the life times explicitly so that the list does not outlive its elements. Unfortunately, wasm-bindgen does not allow for explicit lifetime declarations on exported structs so this option is out.
Ideally, all the logic related to dealing with the vectors should only exist in Rust and be hidden from JavaScript. If you really need to have access to the vectors in both places, the easiest brute-force solution would be to add a getter to the Group and use it as a "master copy". The code would look like this:
#[wasm_bindgen]
#[derive(Copy, Clone)]
pub struct Vector3 {
x: f32,
y: f32,
z: f32,
}
#[wasm_bindgen]
impl Vector3 {
#[wasm_bindgen(constructor)]
pub fn new() -> Vector3 {
Vector3 { x:0.0, y:0.0, z:0.0 }
}
pub fn get_x(&self) -> f32 {
self.x
}
pub fn get_y(&self) -> f32 {
self.y
}
pub fn get_z(&self) -> f32 {
self.z
}
}
#[wasm_bindgen]
pub struct Group{
list: Vec<Vector3>,
}
#[wasm_bindgen]
impl Group {
#[wasm_bindgen(constructor)]
pub fn new() -> Group {
Group { list: vec![] }
}
pub fn add(&mut self, vec: Vector3) {
self.list.push(vec);
}
pub fn get_at(&self, idx: usize) -> Vector3 {
self.list[idx]
}
}
Then, the JavaScript side would look like:
...
setTimeout(() => {
for (let i = 0; i < list.length; i++) {
const vect = group.get_at(i);
console.log(vect.get_x());
}
}, 1000);
...
and we get rid of the list
altogether.
NOTE: this is a REALLY bad way of managing lists because every time you call get_at
you create yet another copy of the vector so if your code is calculation-heavy then memory leaks could be a concern. Unfortunately, wasm-bindgen does not allow borrowed return values so cloning is pretty much the only option if you need to have the entire tuple in one call.
If you don't mind dealing with many small calls on the JS side, then one of the more obvious optimizations would be to split get_at
and convert it into get_x_at
, get_y_at
, get_z_at
thus avoiding the need to carry the Vector instance across the wasm boundary.
Better yet, perhaps you could come up with a different way of splitting the areas of concern so that the vectors would not have to cross the assembly boundary at all.
Hope that helps!
Upvotes: 3