Is it possible to get referential stability in objects returned from rust?
Hey,
I'm pretty new to using wasm-bindgen, so please excuse me if I've missed something obvious. When writing functions that return results that are used by react, it can be helpful to have "referential stability", by which I mean calling the functino twice will result in two objects that are considered equal by Object.is.
Here's a bit of a contrived example
#[derive(Clone)]
#[wasm_bindgen]
struct Foo;
#[wasm_bindgen]
struct Bar {
foo: Foo,
}
#[wasm_bindgen]
impl Bar {
#[wasm_bindgen(constructor)]
pub fn new() -> Self {
Self { foo: Foo }
}
#[wasm_bindgen]
pub fn fo(&self) -> Foo {
self.foo.clone()
}
}
If someone creates a Bar in a react component. perhaps even using useMemo to ensure referential stability for the Bar, this will not translate to any referential stability for bar.foo. A new object will be created and returned each time. How can this issue be avoided? I tried this:
pub fn foo(&self) -> &Foo { // return a reference
{
self.foo.clone()
}
But it just results in a compilation error:
error: cannot return a borrowed ref with #[wasm_bindgen]
--> yap-frontend-rs/src/lib.rs:50:26
|
50 | pub fn foo(&self) -> &Foo {
| ^^^^
So that doesn't seem to work, and I have no other ideas unfortunately. (Maybe you can do something with JsValue since it's just an index, but I would rather return a #[wasm_bindgen] object)
While the Foo object has a new identity in Rust, the underlying JS reference will still be the same object. So you can verify equality using the === JS operator on the underlying JS values using https://docs.rs/wasm-bindgen/latest/wasm_bindgen/struct.JsValue.html#impl-PartialEq-for-JsValue.
I think I don't understand your meaning. (Or more likely, I was just very unclear in my question.) Suppose you have this rust code:
#[derive(Clone)]
#[wasm_bindgen]
struct Foo;
#[wasm_bindgen]
struct Bar {
foo: Foo,
}
#[wasm_bindgen]
impl Bar {
#[wasm_bindgen(constructor)]
pub fn new() -> Self {
Self { foo: Foo }
}
#[wasm_bindgen]
pub fn foo(&self) -> Foo {
self.foo.clone()
}
}
And this react:
function AppMain() {
const bar = new Bar()
const isEqual = bar.foo() === bar.foo();
return <div>bar.foo() === bar.foo(): {isEqual ? 'true' : 'false'}</div>
}
I see this:
bar.foo() === bar.foo(): false
But this is problematic for react, because it means that using the output of a Rust function as a parameter to a React component will cause unnecessary renders (unless it is a primitive type, like string or number)
It's an interesting use case. I suppose this is not handled by wasm bindgen yes.
It could be nice to be able to return references that are wrapped with the appropriate reference counting finalization on the other end via something like pub fn foo(&self) -> Rc<Foo>.
@anchpop A workaround that works today is to convert and store your object as a JsValue ahead of time.
Since it becomes a regular JS value, it preserves referential equality instead of converting same object over and over again:
#[derive(Clone)]
#[wasm_bindgen]
struct Foo;
#[wasm_bindgen]
struct Bar {
foo: JsValue,
}
#[wasm_bindgen]
impl Bar {
#[wasm_bindgen(constructor)]
pub fn new() -> Self {
Self {
foo: JsValue::from(Foo),
}
}
#[wasm_bindgen]
pub fn foo(&self) -> JsValue {
self.foo.clone()
}
}
That is useful, thanks @RReverser! I guess this could be automated with an LRU cache or something
Sure, although that would be more expensive than statically storing converted value only where you know it can be reused.