Sharing Classes between Rust and JavaScript
Tags: rust, webdev, webassembly, javascript
Published: 2020-01-03 21:52:48


# Classes in JavaScript 

The ECMAScript 6 (ES6) introduced [classes](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes) into the JavaScript. But classes are just a syntactic sugar over the prototype-based inheritance in JavaScript.

> It is very important to understand, the JavaScript's inheritance model is not object-oriented.

To declare a class we use the `class` keyword. Similar to functions, classes can be either declared alone or expressed as an expression (assigning this to a variable).

> For non-JavaScript developers, prototypical inheritance is important to understand.

Prototypical inheritance - If an object A can point to another object B, then object A is the prototype of object B. Thus object B has the qualities of both A and B. Whenever we look up for a property in object B, we will have to look for the property in object A, if it is not present in B.

So in JavaScript (almost) everything is an `Object` instance. These objects sit on top of a prototype chain. The prototypal model is more powerful. In JavaScript [objects](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/prototype) are dynamic bags of properties.


---

The wasm-bindgen provides an option to share the classes between JavaScript and WebAssembly. That is, we define a class in Rust and use the same class in the JavaScript.

---

Check out my book on Rust and WebAssembly [here](https://sendilkumarn.com/wasm-book)

---

The wasm-bindgen uses annotations.

Let us see how easy it is to share classes between JavaScript and WebAssembly (Rust).

## Class from Rust to JavaScript

### Write some code ✍️

Create a new project.

```
$ cargo new --lib class_world
Created library `class_world` package
```

Define the `wasm-bindgen` dependency for the project, Open the cargo.toml file and add the content.

```
[package]
name = "class_world"
version = "0.1.0"
authors = ["Sendil Kumar <sendilkumarn@live.com>"]
edition = "2018"

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

[dependencies]
wasm-bindgen = "0.2.56"
```

Open the `src/lib.rs` file and replace the contents with the following.

```
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub struct Point {
    x: i32,
    y: i32,
}

#[wasm_bindgen]
impl Point {
    pub fn new(x: i32, y: i32) -> Point {
        Point { x: x, y: y}
    }
    
    pub fn get_x(&self) -> i32 {
        self.x
    }

    pub fn get_y(&self) -> i32 {
        self.y
    }

    pub fn set_x(&mut self, x: i32) {
        self.x = x;
    }

    pub fn set_y(&mut self, y:i32) {
        self.y = y;
    }
    
    pub fn add(&mut self, p: Point) {
        self.x = self.x + p.x;
        self.y = self.y + p.y;
     }
}
```

We described the class with a [Struct](https://doc.rust-lang.org/rust-by-example/custom_types/structs.html) in Rust. 

The `Point` Struct is implemented with getters, setters and an add function that takes in x and y coordinate and return the sum. 

This is a normal Rust code with only the `#[wasm_bindgen]` annotation added.

> Note that both the definition of the struct and the all the implementation methods are made public explicitly using `pub` keyword.

To generate the WebAssembly module using Cargo:

```
$ cargo build --target=wasm32-unknown-unknown
```

Use the `wasm-bindgen CLI` to generate the binding file for the WebAssembly module generated. If you do not have wasm-bindgen available then check out [this](https://dev.to/sendilkumarn/rust-and-webassembly-for-the-masses-wasm-bindgen-57fl) post on how to install them.

```
$ wasm-bindgen target/wasm32-unknown-unknown/debug/class_world.wasm --out-dir .
```

This generates the binding JavaScript file, the type definition files, and the WebAssembly Module.

```
452B  class_world.d.ts
2.0K  class_world.js
456B  class_world_bg.d.ts
41K   class_world_bg.wasm
```

---

### Inside the binding file

Let us look at the `class_world.js`:

This is the binding file generated by the `wasm-bindgen`. Similar to the [previous case](https://dev.to/sendilkumarn/rust-and-webassembly-for-the-masses-wasm-bindgen-57fl) it consists of `TextDecoder`, `getStringFromWasm0`, `getUint8Memory0`. Additionally it consists of a class `Point`.

The type signature of `Point` class is similar to what we have defined inside the Rust. The getters, setters and the add function. Additionally in every method we are asserting the type of the input is `Point`. Since WebAssembly is strictly typed, we need to have this type check.

Additionally, the wasm-bindgen produces a [static method](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/static) `__wrap` that creates the `Point` class object and attaches a pointer to it. 

It adds a `free` method that in turn calls the `__wbg_point_free` method inside the WebAssembly module. This method is responsible for freeing up the memory taken by the Point object or class.


Please copy over the `package.json`, `index.js`, and `webpack-config.js` from the previous [post]((https://dev.to/sendilkumarn/rust-and-webassembly-for-the-masses-wasm-bindgen-57fl)). Then run `npm install`. Modify the `index.js` with the following content.

```
import("./class_world").then(({Point}) => {
 const p1 = Point.new(10, 10);
 console.log(p1.get_x(), p1.get_y());
 const p2 = Point.new(3, 3);
 p1.add(p2);
 console.log(p1.get_x(), p1.get_y());
});
```

We are importing the binding file, that will in turn import the WebAssembly module.

We call the new method in the Point class and pass it "x" and "y". We print the "x" and "y" co-ordinates. This prints `10, 10`. Similarly we create another Point (p2).

Finally, we call the add function and pass it Point p2. This prints `13, 13`. 

The getter method uses the pointer and fetches the value from the shared array buffer.

```
get_x() {
    var ret = wasm.point_get_x(this.ptr);
    return ret;
}
```

In the setter method, we pass in the pointer and the value. Since we are just passing in a number here. There is no extra conversion needed.

```
/**
 * @param {number} x
 */
 set_x(x) {
    wasm.point_set_x(this.ptr, x);
 }
```

In case of add, we take the argument and get the pointer to the Point object and pass it to the WebAssembly module.

```
add(p) {
    _assertClass(p, Point);
    var ptr0 = p.ptr;
    p.ptr = 0;
    return wasm.point_add(this.ptr, ptr0);
}
```

The wasm-bindgen makes it easy and simple to convert a class into WebAssembly module.

---

👇 Repo 👇

{% github sendilkumarn/rustwasm-rust-to-js no-readme %}

---

## Class from JavaScript to Rust

We have seen how to create a class in Rust and call it in the JavaScript world. Let us pass the class from JavaScript world to Rust.

In JavaScript, classes are objects with some methods. Rust is a [strictly typed language](https://en.wikipedia.org/wiki/Type_system#Dynamic_type_checking_and_runtime_type_information). This means the Rust compiler needs to have concrete bindings. Without them, the compiler complains about it. We need a way to assure the compiler that during the runtime will have this API available.

The `extern "C"` function block comes here to help. The `extern "C"` makes a function name available in Rust. Read more about them [here](https://doc.rust-lang.org/nomicon/ffi.html).

### Write some code ✍️

Let us create a new project.

```
$ cargo new --lib class_from_js_world
Created library `class_from_js_world` package
```

Define the wasm-bindgen dependency for the project, Open the cargo.toml file and add the content.

```
[package]
name = "class_from_js_world"
version = "0.1.0"
authors = ["Sendil Kumar <sendilkumarn@live.com>"]
edition = "2018"

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

[dependencies]
wasm-bindgen = "0.2.56"
```

Please copy over the `package.json`, `index.js`, and `webpack-config.js` from the previous example. Then run `npm install`.

Open the `src/lib.rs` file and replace the contents with the following.

```
use wasm_bindgen::prelude::*;

//1
#[wasm_bindgen(module = "./point")]
extern "C" {
    //2
    pub type Point;

    //3
    #[wasm_bindgen(constructor)]
    fn new(x: i32, y: i32) -> Point;

    //4
    #[wasm_bindgen(method, getter)]
    fn get_x(this: &Point) -> i32;

    #[wasm_bindgen(method, getter)]
    fn get_y(this: &Point) -> i32;

    //5
    #[wasm_bindgen(method, setter)] 
    fn set_x(this: &Point, x:i32) -> i32;

    #[wasm_bindgen(method, setter)]
    fn set_y(this: &Point, y:i32) -> i32;
    
    // 6
    #[wasm_bindgen(method)] 
    fn add(this: &Point, p: Point);
}

#[wasm_bindgen]
//7
pub fn get_precious_point() -> Point {
    let p = Point::new(10, 10);
    let p1 = Point::new(3, 3);
    // 8
    p.add(p1); 
    p
}
```

At //1, we are importing the JavaScript module. This `#[wasm_bindgen]` annotation import a JavaScript file `point.js`.

> Note the WebAssembly module does not know where to look up for the file. By convention, this JavaScript file should be present along with `Cargo.toml`. The `wasm-bindgen-CLI` looks up in that location and links the file while generating the WebAssembly Module.

#### extern "C"

Then we create an `extern "C"` block to define the methods that we need to use. The `extern "C"` block gives the type assurances during the compilation phase.

We first declare a type signature in the block (`pub type Point;`). We use this as any other type inside the Rust code. 

Then we define the constructor. We pass in the constructor as an argument to the wasm_bindgen annotation. This simplifies and reduces the verbose declaration. The `wasm-bindgen` will do all the necessary code generation for the constructor.

Then we define a function that takes in arguments and return with their type signatures. 

These functions bind to the namespace of the Point type, and we can call `Point::new(x, y);` inside the Rust function.

//4 and //5 is getters and setters respectively.

//6 is the `add` method. The `add` method has the `#[wasm_bindgen(method)]` annotation.

//7 is where we export `get_precious_point()` function using `#[wasm_bindgen]` annotation.


#### Point class in JavaScript

Create `Point.js` with the following contents:

```
export class Point {
    constructor(x, y) {
        this.x = x;
        this.y = y;
    }

    get_x() {
        return this.x;
    }

    get_y() {
        return this.y;
    }

    set_x(x) {
        this.x = x;
    }

    set_y(y) {
        this.y = y;
    }

    add(p1) {
        this.x += p1.x;
        this.y += p1.y;
    }
}
```

Finally, replace the index.js with the following:

```
import("./class_from_js_world").then(module => {
    console.log(module.get_precious_point());
});
```

To generate the WebAssembly module using Cargo:

```
$ cargo build --target=wasm32-unknown-unknown
```

Use the `wasm-bindgen CLI` to generate the binding file for the WebAssembly module generated. If you do not have wasm-bindgen available then check out [this](https://dev.to/sendilkumarn/rust-and-webassembly-for-the-masses-wasm-bindgen-57fl) post on how to install them.

```
$ wasm-bindgen target/wasm32-unknown-unknown/debug/class_from_js_world.wasm --out-dir .
```


Run this on the browser using `npm run serve`. The console will print the point object (class).

---

### Cargo Expand 

Let us see how the `#[wasm_bindgen]` macro is expanding the code.

```
$ cargo expand --target=wasm32-unknown-unknown > expanded.rs
```

There are a few interesting things happening here.

* The type `Point` is converted into a `Struct`. This is similar to what we have done on the previous example. 

* The struct's members are `JSValue` instead of Number (`x` and `y`). This is because wasm_bindgen will not know what this point class is instantiating. So it creates a JavaScript object and makes that as its member.

```
pub struct Point {
    obj: wasm_bindgen::JsValue,
}
```

It also defines how to construct the Point object and how to dereference it. Useful for the WebAssembly runtime to know when to allocate and when to dereference it.

All the methods that are defined are converted into the implementation of the Point struct. As you can see there is a lot of unsafe code in the method declaration. This is because the Rust code interacts directly with the raw pointers.

```

fn new(x: i32, y: i32) -> Point {

 #[link(wasm_import_module = "__wbindgen_placeholder__")]
 extern "C" {
 fn __wbg_new_3ffc5ccd013f4db7(x:<i32 as ::wasm_bindgen::convert::IntoWasmAbi>::Abi,
 y:<i32 as ::wasm_bindgen::convert::IntoWasmAbi>::Abi)
 -> <Point as ::wasm_bindgen::convert::FromWasmAbi>::Abi;
 }

 unsafe {
 let _ret = {
 let mut __stack = ::wasm_bindgen::convert::GlobalStack::new();
 let x = <i32 as ::wasm_bindgen::convert::IntoWasmAbi>::into_abi(x, &mut __stack);
 let y = <i32 as ::wasm_bindgen::convert::IntoWasmAbi>::into_abi(y, &mut __stack);
 __wbg_new_3ffc5ccd013f4db7(x, y)
 };

 <Point as ::wasm_bindgen::convert::FromWasmAbi>::from_abi(_ret,
 &mut ::wasm_bindgen::convert::GlobalStack::new())
 }
}
```

As shown above, the code generated by `#[wasm_bindgen(constructor)]` macro. It first links the code with extern "C" block. The arguments are then cast such that they are inferred in the WebAssembly runtime.


Then the unsafe block. First, space is reserved in the global stack. Then both "x" and "y" are converted into type "IntoWasmAbi".

> The `IntoWasmAbi` is a trait for anything that can be converted into a type that can cross the wasm ABI directly, eg., u32 or f64.

Then the function in the JavaScript is called.  The returned value is then cast into Point type using FromWasmAbi.

> The `FromWasmAbi` is a trait for anything that can be recovered by-value from the WASM ABI boundary, eg., a Rust u8 can be recovered from the WASM ABI u32 type.

Check out more about IntoWasmAbi and FromWasmAbi [here](https://rustwasm.github.io/wasm-bindgen/api/wasm_bindgen/convert/trait.FromWasmAbi.html)  and  [here](https://rustwasm.github.io/wasm-bindgen/api/wasm_bindgen/convert/trait.IntoWasmAbi.html).
---

If you have enjoyed the post, then you might like my book on Rust and WebAssembly. Check them out [here](https://sendilkumarn.com/wasm-book)

---
--- 

👇 Repo 👇

{% github sendilkumarn/rustwasm-js-to-rust no-readme %}

---

In the next post we will see how to access JavaScript APIs in the Rust.

---

You can follow me on [Twitter](https://twitter.com/sendilkumarn).

If you like this article, please leave a like or a comment. ❤️

---