Set up wasm-bindgen for easy Rust/JavaScript Interoperability

Nik Graf
InstructorNik Graf
Share this video with your friends

Social Share Links

Send Tweet
Published 6 years ago
Updated 4 years ago

Interoperability between JavaScript and Rust is limited to numerics and accessing memory directly. Since this can be exhausting and overwhelming to do manually the Rust/Wasm team has created the wasm-bindgen project to facilitate high-level interactions between Rust and JavaScript.

Instructor: [00:00] WebAssembly programs operate on a limited set of value types. Due to this, the functions bridging between JavaScript and Rust only allow for primitive numeric types, like integer or float. Let me demonstrate this to you.

[00:15] Here, we have a function, appendStringToBody, that accepts a text and adds it to the body. In our utils library, we import the function and invoke it with a string. If we run our example, we will see only a number being appended to the document.

[00:31] This is not what we wanted to achieve here. Fortunately, we can work around this by directly reading or writing to WebAssembly's memory using JavaScript. Each WASM module has a linear memory, which is initialized during instantiation.

[00:46] While sometimes accessing memory directly can be useful, in most cases, it's quite cumbersome. That's why the generic bindgen-style framework, wasm-bindgen, was created. The framework makes it possible to write idiomatic Rust function signatures that map to idiomatic JavaScript functions automatically.

[01:07] To get started, we add wasm-bindgen as a dependency to our Cargo configuration. Then we use the extern create declaration to link the wasm-bindgen create to this new library. After that, we use the use declaration to make all functions from wasm-bindgen preload.

[01:29] Now, our setup is complete, and we can use the wasm-bindgen annotation to inform the compiler that the following construct needs to be interoperable between Rust and JavaScript. We compile our Rust library using wasm-pack build.

[01:45] This generates a package directory, containing a WASM file and the matching JavaScript module, for putting the necessary abstractions in-place to interact with our WebAssembly code. This way, we can pass types, like strings, into our Rust code without manually having to take care of the conversion.

[02:08] We can see our run as well as our appendStringToBody function are in there. This is great, because now, we can import this module, use it, and work with other than just primitive numeric types. The generated code will take care of the proper serialization.

[02:26] There is one gotcha, though. Currently, this file includes some import features not yet implemented in our modern browsers. Using a JavaScript bundler, we can set up a project that bundles our code to one JavaScript file.

[02:41] In this example, we're going to use webpack. We start out by creating a package.json, and then installing webpack, the webpack CLI, and the webpack dev server. Then we create a webpack.config.js file, and fill it with the minimal configuration.

[03:13] Now, we create our index.js file, where we import our utils module dynamically, and wait for the promise to resolve to then invoke run. Currently, this dynamic import is necessary to load a WASM module. It is a known limitation in webpack, and hopefully will be resolved soon.

[03:33] Next, we need to update our index.html file to only load the index.js script. This leads to an interesting factor. We can't provide our appendStringToBody manually anymore, since loading the WebAssembly file is done inside our generated utils.js file.

[03:55] What can we do instead is create a new module and export our function. When using wasm-bindgen, we then can declare that the module should be imported. We rebuild our project using wasm-pack build again, and then we review the new utils.js file.

[04:15] Here, we can see the function import from DOM utils. At last, we can start the webpack dev server using MPX webpack dev server, and verify that, indeed, you could pass a string between Rust and JavaScript.

[04:33] As expected, it shows hello world instead of 1024. wasm-bindgen took care of passing the string from Rust to JavaScript. While we only demonstrated passing a string from Rust to JavaScript, it also works the other way around.

Malachiain
Malachiain
~ 5 years ago

I think export const appendStringtoBody = (value) => { in the transcript should be export const appendStringToBody = (value) => {. I hope this helps someone else.

Giuseppe Cardella
Giuseppe Cardella
~ 5 years ago

If you get a relative path error: error: relative module paths aren't supported yet 5 | #[wasm_bindgen(module="../domUtils")]

Put the domUtils.js inside of the /pkg and change the line in lib.rs from #[wasm_bindgen(module="../domUtils")] to #[wasm_bindgen(module="domUtils")] and rerun wasm-pack build

hkwid
hkwid
~ 5 years ago

error: relative module paths aren't supported yet 5 | #[wasm_bindgen(module="../domUtils")]

To fix the error of relative path, you need to change [wasm_bindgen(module="../domUtils")] -> [wasm_bindgen(module="/domUtils")]. In this case / means root of the project.

When you move domUtils, you will face another error from webpack-dev-server.

[Reference] https://rustwasm.github.io/docs/wasm-bindgen/reference/js-snippets.html?highlight=relative,path#caveats

Nik Graf
Nik Grafinstructor
~ 5 years ago

Thanks Giuseppe & hkwid for the hints!

EdmundsEcho
EdmundsEcho
~ 5 years ago

A recent fix to the "can't load relative module" is to set the attribute raw_module= "../domUtils"

Jeremy Swanborough
Jeremy Swanborough
~ 4 years ago

raw module binding is the only thing that worked for me, thanks @EdmundsEcho

Markdown supported.
Become a member to join the discussionEnroll Today