Wasm_bindgen + Clone

#[wasm_bindgen::prelude::wasm_bindgen(module = "class_ts")]
extern "C" {
    type Msg_All;
}

Msg_All is a type declared in TypeScript. I am trying to use it from Rust/wasm32.

Question: how do I mark Msg_All as clone? So I can do things like:

#[derive(Clone)]
struct Foo {
  t: Msg_All;
}

?

You can just write #[derive(Clone)] directly on the type declaration.

#[wasm_bindgen(module = "class_ts")]
extern "C" {
    #[derive(Clone)]
    type Msg_All;
}
1 Like

This is surprising to me.

Internally, how does this work? I don't understand what #[derive(Clone)] means on an opaque type.

I'm guessing any attributes added to the type Msg_All are copied directly to the struct Msg_All that wasm-bindgen generates. The Msg_All struct is just a wrapper around a wasm_bindgen::JsValue so all the normal #[derive(Clone)] rules apply.

1 Like

The #[wasm_bindgen] attribute is a procedural macro that takes the extern block you've put it on and emits some other code. It could be that the wasm_bindgen macro interprets the derive itself or it could be that the derive is passed through onto whatever type gets emitted by the macro.

Either way, the Msg_All type which gets made emitted isn't a proper opaque type; those are still unstable. Instead, something along the lines of struct Msg_All(JsValue); gets generated, and the postprocessing step and/or Javascript wrapper do the translation from the Javascript object to an i32 key which is actually what JsValue is.

https://rustwasm.github.io/wasm-bindgen/reference/reference-types.html

Wasm has an externref type kind for working with external objects, but Rust can't work with them directly. externref can't be put into linear memory, so you can't take references to them; instead, they must be put into a table and the wasm can put the index into the table into linear memory and take references to that key.

The glue then translates between that key and actually using the externref value. Which side that glue is on depends on whether you've enabled externref usage.

1 Like

To be pedantic, do you agree with the following statements:

  1. Regardless of how complicated the value is on the JS side, the Rust side merely copies a table entry.

  2. Thus, the copy is 'shallow' and on the Rust side the cost is that of cloning a Rc<void*>

  3. As a followup, the clone & the original share the JS-side of the value, so modifying one will result in modifying the other.

Cloning the JsValue currently calls __wbindgen_object_clone_ref and creates a new JsValue with a new table index. I don't have any real experience using wasm_bindgen nor insight into their magic symbols, but I do believe that this just duplicates the entry in the externref table, and I do believe that for Javascript objects has identical semantics to var new_obj = old_obj; in Javascript (i.e. referencing the same object unless it's a primitive value).

The approximate cost is unfortunately difficult to predict. With externref mode, it's just making a copy of the externref, but what that means is AIUI entirely up to the wasm implementation. It's fairly clear it's supposed to reference the same external value, and the cost should be on the order of cloning Arc (though with a lower limit on the number of active clones than Arc). If you're on a reasonable wasm Implementation, this should hold.

1 Like