DEV Community

loading...

Access JavaScript API with Rust

sendilkumarn profile image Sendil Kumar N Originally published at sendilkumarn.com ใƒปUpdated on ใƒป6 min read

Alt Text

JavaScript is everywhere. It enabled billions of people to develop and deliver projects. JavaScript is easy to get started. It is for a reason, JavaScript is one of the highly used programming language.

It is true that there are weird corners in the language. But believe me every programming language out there has those corners. Check this talk here.

Javascript is a dynamically typed language. It is one of its main advantage as well as disadvantage. Dynamic typing makes JavaScript API easy to write and understand. That is contextually simpler for people. But during compilation, the compiler has to do the hard work. This often leads to runtime exception and unpredictable performance.

Being a widely used language, JavaScript comes with a lot of bells and whistles. These features provides an elegant API (not talking about smooshmap). JavaScript provides a rich API to work with Objects, Arrays, Maps, Sets, and others.


WebAssembly provides strict typing and predictable performance. The performance is much faster than compared with JavaScript. Refer ๐Ÿ‘‡

But WebAssembly is not always faster, there are scenarios where JavaScript is faster than the WebAssembly module. For example, to access a DOM JavaScript is much faster than compared with WebAssembly module. The boundary crossing has an impact. During those times it is great to use JavaScript to have higher performance.


Check out my book on Rust and WebAssembly here


Alt Text

JavaScript and WebAssembly needs to work closely in an application. The JavaScript engine needs to provide seamless integration between the JavaScript and WebAssembly. Check out here about how Firefox made calls between JavaScript and WebAssembly faster.

For a seamless integration between JavaScript and WebAssembly, it is important that both should understand each other. The JavaScript should provide the necessary context to enable languages like Rust too interoperate. But it is a tedious process to write the necessary bindings between JavaScript and Rust. Handcrafting the bindings is a mundane process.

But what if we have bindings to those APIs, common API, that is present in both Node.js and Browser environment.

The rustwasm team's answer to that is the js-sys crate.


JS-Sys Crate

The js-sys crate contains raw #[wasm_bindgen] bindings to all the global APIs guaranteed to exist in every JavaScript environment by the ECMAScript standard. - RustWASM

The js-sys crate provide bindings to the JavaScript's standard built-in objects, including their methods and properties.


Write some code โœ๏ธ

Create a default project with cargo new command.

$ cargo new --lib jsapi
Enter fullscreen mode Exit fullscreen mode

Please copy over the package.json, index.js, and webpack.config.js from the previous post.

Change the contents of Cargo.toml:

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

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

[dependencies]
wasm-bindgen = "0.2.56"
js-sys = "0.3.33"
Enter fullscreen mode Exit fullscreen mode

We added js-sys = "0.3.33" as a dependency. Now open the src/lib.rs and replace the file with the following contents.

use wasm_bindgen::prelude::*;

use js_sys::Map;

#[wasm_bindgen]
pub fn new_js_map() -> Map {
    Map::new()
}
Enter fullscreen mode Exit fullscreen mode

We imported the wasm_bindgen library. Then imported map from the js_sys crate. The js_sys crate provides all the necessary JavaScript API information.

In the function new_js_map we return a Map of type js_sys::Map. The js_sys crate is responsible for defining the type information for Rust and providing all the APIs. To create a new Map we simply call Map::new().

That is it, we created a JavaScript Map inside Rust. Now we can access this map inside the Rust and pass it to the JavaScript world as a JavaScript Map.

Note that In Rust, return keyword is optional. The last line without a semicolon is considered a return statement.


Then we create a function to create a Map, set values into the map and retrieve it.

#[wasm_bindgen]
pub fn set_get_js_map() -> JsValue {
    let map = Map::new();
    map.set(&"foo".into(), &"bar".into());
    map.get(&"foo".into())
}
Enter fullscreen mode Exit fullscreen mode

We created a function set_get_js_map, it is annotated with #[wasm_bindgen]. It returns JSValue. This is a wrapper used by Rust for specifying the JavaScript values. The JSValue type is defined in the js_sys crate.

JsValue is a representation of an object owned by JS. A JsValue doesn't actually live in Rust right now but actually in a table owned by the wasm-bindgen generated JS glue code. Eventually the ownership will transfer into wasm directly and this will likely become more efficient, but for now it may be slightly slow.

We are creating a new map using the Rust syntax. We set the value into the map. Instead of simply accepting String type, the map.set or map.get accepts a pointer to the JsValue. Rust provides value to value converter into function, that converts the value from Rust's str type into the JsValue type.

Finally we are getting the value from the map using map.get function call. This returns "bar" as the output as a JavaScript Value (JsValue).

We can run through the map using foreach inside the Rust code like below:

#[wasm_bindgen]
pub fn run_through_map() -> f64 {
    let map = Map::new();
    map.set(&1.into(), &1.into());
    map.set(&2.into(), &2.into());
    map.set(&3.into(), &3.into());
    map.set(&4.into(), &4.into());
    map.set(&5.into(), &5.into());
    let mut res: f64 = 0.0;

    map.for_each(&mut |value, _| {
        res = res + value.as_f64().unwrap();
    });

    res
}
Enter fullscreen mode Exit fullscreen mode

This creates a map and then loads the map with values 1, 2, 3, 4, 5. Then runs over the created map and adds the value together. This produces an output of "15" (i.e., 1 + 2 + 3 + 4 + 5).

Lastly, we replace the index.js with the following contents.

import("./jsapi").then(module => {
    let m = module.new_js_map();
    m.set("Hi", "Hi");

    console.log(m); // prints Map { "Hi" ->  "Hi" }

    console.log(module.set_get_js_map());  // prints "bar"

    console.log(module.run_through_map()); // prints 15
});
Enter fullscreen mode Exit fullscreen mode

To run the above code, first compile the Rust into WebAssembly module by using:

cargo build --target="wasm32-unknown-unknown"
Enter fullscreen mode Exit fullscreen mode

Then run

wasm-bindgen target/wasm32-unknown-unknown/debug/jsapi.wasm --out-dir .
Enter fullscreen mode Exit fullscreen mode

to generate the JavaScript bindings for the WebAssembly module.

Finally install the dependencies using npm install and run npm run serve. Now spin up the browser and open the developer console to see the expected results.


What happens here?

Let us start with the generated JavaScript binding file. The generated binding files have almost the same structure as above, but with a few more functions exported.

The heap object is used as a stack here. All the JavaScript objects that are shared or referenced with the WebAssembly modules are stored in this heap. It is also important to note that once the value is accessed it is popped out from the heap.

The takeObject function is used to fetch the object from the heap. It first gets the object at the given index. Then it removes the object from that heap index (i.e., pops it out). Finally, it returns the value.

function takeObject(idx) {
    const ret = getObject(idx);
    dropObject(idx);
    return ret;
}
Enter fullscreen mode Exit fullscreen mode



If you have enjoyed the post, then you might like my book on Rust and WebAssembly. Check them out here


Do you know RustWASM enables you to use webAPIs too, check out


Similarly, we can use JavaScript APIs inside the Rust. The bindings are only generated for the common JavaScript API (including Node.js and the browser). Check out here for all the supported API here.

Check out more about JavaScript APIs here

Check out more about from and into here


You can follow me on Twitter.

If you like this article, please leave a like or a comment. โค๏ธ


Picture Courtesy: JS Gif - https://www.jstips.co/en/about/

Discussion (0)

pic
Editor guide