Understanding access to wasm bindgen structs

Hello,

first time wasm bindgen user here. What I’m trying to do: create a rust object and then use it bi-directional in JavaScript. Now I can access and update the object properties directly (o.y), but I can’t seem to change the nested struct with for example the following statement o.struct1.h = 3. I suspect I’m working on a copy, because Struct1 requires Copy and Clone. Why does it need Copy and Clone?

these lines work:

console.log(o.getH()+"|"+o.struct1.getH()); //answer: 10 | 10 ok!
o.setH(3);
console.log(o.getH()+"|"+o.struct1.getH()) //answer: 3 | 3 ok!

this is also “ok” (but why are these integers now?):

console.log(o.y); //answer: 0 ok!
o.y = 3.0;
console.log(o.y); //answer: 3 ok!

these don’t:

o.struct1.h=3;
o.struct1.setH(3);
console.log(o.getH()+"|"+o.struct1.getH()) //answer: 10 | 10 (not OK!)

same:

o.getStruct1().h=3;
o.getStruct1().setH(3);
console.log(o.getH()+"|"+o.struct1.getH()) //answer: 10 | 10 (not OK!)

Cargo.toml

[package]
name = "t"
version = "0.0.1"
edition = "2018"

[lib]
crate-type = ["cdylib"]

[dependencies]
wasm-bindgen = "*"

[dependencies.web-sys]
version = "*"

[profile.release]
lto = true

index.html

<!DOCTYPE html>
<html>
<head>
	<script type="module">
		import {default as init, MyThing} from '/t/pkg/t.js';

		async function run() {
			await init('/t/pkg/t_bg.wasm');

			let o = new MyThing();
         /*   
            console.log(o.getH()+"|"+o.struct1.getH()); //answer: 10 | 10 ok!
			o.setH(3);
            console.log(o.getH()+"|"+o.struct1.getH()) //answer: 3 | 3 ok!
            */
			
            /*console.log(o.y); //answer: 0 ok!
			o.y = 3.0;
			console.log(o.y); //answer: 3 ok!
*/

            /*o.struct1.h=3;
			o.struct1.setH(3);
			console.log(o.getH()+"|"+o.struct1.getH()) //answer: 10 | 10 (not OK!)
*/

            o.getStruct1().h=3;
            o.getStruct1().setH(3);
			console.log(o.getH()+"|"+o.struct1.getH()) //answer: 10 | 10 (not OK!)
		}

		run();
	</script>
</head>
<body>
</body>
</html>

lib.rs

use wasm_bindgen::prelude::*;

#[wasm_bindgen(start)]
pub fn main() -> Result<(), JsValue> {
	Ok(())
}

#[wasm_bindgen]
#[derive(Copy, Clone)] //why does it need Copy and Clone?
pub struct Struct1 {
	pub h: u32,
}

#[wasm_bindgen]
impl Struct1 {
	#[wasm_bindgen(method, js_name = "setH")]
	pub fn set_h(&mut self, h: u32) {
		self.h = h;
	}
	#[wasm_bindgen(method, js_name = "getH")]
	pub fn get_h(&self) -> u32 {
		self.h
	}
}

#[wasm_bindgen]
pub struct MyThing {
	pub struct1: Struct1,
	pub y: f32,
}

#[wasm_bindgen]
impl MyThing {
	#[wasm_bindgen(constructor)]
	pub fn new() -> MyThing {
		MyThing {
			y: 0.0,
			struct1: Struct1 {
				h: 10,
			},
		}
	}

	#[wasm_bindgen(method, js_name = "getStruct1")]
	pub fn get_struct1(&mut self) -> Struct1 { //ah, here it is copied? Can't return &mut Struct1
		self.struct1
	}

	#[wasm_bindgen(method, js_name = "setH")]
	pub fn set_h(&mut self, h: u32) {
		self.struct1.h = h;
	}
	#[wasm_bindgen(method, js_name = "getH")]
	pub fn get_h(&self) -> u32 {
		self.struct1.h
	}
}

Maybe this is just a bug? But I don’t think so. I’m still learning. I don’t understand why o.setH(3); works but the very similar o.struct1.setH(3); does not. It’s puzzling…

As I understand it, it’s because of lifetimes. If o.struct1 returned its value by reference, there’s no easy way to guarantee that the value will stay in scope on the Rust side for the entire life of the JS wrapper object, which would be required for safety.
Putting the struct behind an Rc<RefCell> inside a new wrapper struct should work, though.

ah! Thank you. I will try it. I must say that I expected the compiler to give me warning instead, because Rust usually does a very good job at babysitting me :slight_smile: