Search code examples
rustwasm-bindgen

Rust wasm-bindgen struct with string


I'm trying to export the following struct:

#[wasm_bindgen]
#[derive(Eq, PartialEq, Debug, Clone)]
pub enum TokenType {
    KeywordLiteral,
    NumberLiteral,
    Operator,
    Separator,
    StringLiteral,
}

#[wasm_bindgen]
#[derive(Eq, PartialEq, Debug, Clone)]
pub struct Token {
    pub typ: TokenType,
    pub val: String,
}

but I'm getting:

error[E0277]: the trait bound `token::TokenType: std::marker::Copy` is not satisfied
  --> src\tokenizer\token.rs:17:14
   |
14 | #[wasm_bindgen]
   | --------------- required by this bound in `__wbg_get_token_typ::assert_copy`
...
17 |     pub typ: TokenType,
   |              ^^^^^^^^^ the trait `std::marker::Copy` is not implemented for `token::TokenType`

as well as:

error[E0277]: the trait bound `std::string::String: std::marker::Copy` is not satisfied
  --> src\tokenizer\token.rs:18:14
   |
14 | #[wasm_bindgen]
   | --------------- required by this bound in `__wbg_get_token_val::assert_copy`
...
18 |     pub val: String,

I can add #[derive(Copy)] to TokenType but not to String.

I'm new to rust so help is really appreciated.


Solution

  • According to wasm-bindgen#1985, public fields of structs are required to be Copy in order for the automatically generated accessors to function.

    You can either make the fields private, or annotate them with #[wasm_bindgen(skip)], and then implement a getter and setter directly.

    There is an example provided in wasm-bindgen's docs describing how to write these:

    #[wasm_bindgen]
    pub struct Baz {
        field: i32,
    }
    
    #[wasm_bindgen]
    impl Baz {
        #[wasm_bindgen(constructor)]
        pub fn new(field: i32) -> Baz {
            Baz { field }
        }
    
        #[wasm_bindgen(getter)]
        pub fn field(&self) -> i32 {
            self.field
        }
    
        #[wasm_bindgen(setter)]
        pub fn set_field(&mut self, field: i32) {
            self.field = field;
        }
    }
    

    When objects are shared between wasm and js, the js object only contains the pointer to the struct inside the wasm runtime's memory. When you access fields from JS, it goes through a defined property which makes a function call to the wasm code asking it for the property value (you can think of the wasm memory as a bit UInt8Array).

    Requiring the objects to be Copy is probably done to avoid surprises when auto-generating these getters and setters. If you implement them manually, you can have the same behaviour in JS and be able to control what's being set on the rust side.