SECRET OF CSS

How To Pass Arrays Between JavaScript and WASM in Rust | by Tommaso De Ponti | Sep, 2022


Exploring this friendly interface

0*1MIcJ2HF0WDnHBm5
Photo by Jakob Søby on Unsplash

Due to WASM’s memory model, passing arrays (and nested arrays) might be quite challenging if you’re trying to do things securely since wasm memory stores its values as array buffers of scalar types.

However, we luckily have the wasm-bindgen (and js-sys) crates that provide a friendly interface to work on when exchanging data between Javascript and WebAssembly.

I should also mention that whenever you are working with WebAssembly, you should always avoid exchanging data between JS and your WASM binary if you are developing with performance as one of your goals.

Generally, we want WASM to work with heavy computations, query, and data in its linear memory and only return small amounts of data due to the computations.

Wasm functions take and return scalar values as an ArrayBuffer. Luckily wasm-bindgen helps with a lot of stuff, but it doesn’t do everything.

Let’s look at how you can pass (or return) arrays using the type.

Since we are working with WebAssembly, more specifically, that targets a web build (it’s meant to work along with js), the best setup we can get is by using wasm-bindgen with its procedural macros.

You can take a look at this post for a more detailed and extensive introduction to the setup and WASM+Rust in general, but here’s a small recap:

Adding the needed dependencies

Add these two dependencies in your Cargo.toml configs (make sure you use the most recent versions):

wasm-bindgen = "0.2.82"
js-sys = "0.3.59"

WASM Optimizations

You should also add the following to your configs:

[profile.release]
opt-level = "z"
overflow-checks = true
debug = 0
strip = "symbols"
debug-assertions = false
panic = "abort"
codegen-units = 1
lto = true
[profile.dev]
opt-level = 0
panic = 'abort'

These will add some settings when compiling your code which is also beneficial in terms of code size (for example, aborting when panicking to shrink code size).

Lastly, you’ll need to tell the compiler that the compilation will produce a dynamic system library ("cdylib") and statistically linked executables ("rlib" contents are inserted in the executable).

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

As I hinted by importing the js_sys dependency, we are going to use the js_sys::Array type.

Let’s say we have a Rust function my_funct that takes an array as a parameter. This is what it would look like using the js_sys::Array type:

#[wasm_bindgen] 
pub fn my_funct(
my_arr: Array,
)

That’s a very simple construct that requires almost no effort from us. However, the effort lies in how to deal with this Array type. For example, you can’t have a nested array, and the value type is unknown (since JS doesn’t have strong typing).

Nested arrays

As I mentioned, the Array type does not provide support for nested arrays. That’s because you can’t cast an element of Array to a vector.

You have a couple of options here. You could pass [["hey"], ["tdep"]], then parse the elements in the array into a string with:

my_arr.get(index).as_string().unwrap()

And then deserialize the string with a library like serde_json.

Another option, which I prefer since it allows me to work with nested arrays without using an additional dependency, is to pass multidimensional arrays as a one-dimensional array and then build it into a nested array on Rust.

This is fairly easy if you know the length of the array. Say you know that you have to take an array of four elements and then construct a nested one with two arrays looking like this [["el1", "el2"], ["el3", "el4"]] . If that is the case, you can easily solve the problem with:

[
[
&arr.get(0).as_string().unwrap(),
&arr.get(1).as_string().unwrap(),
],
[
&arr.get(2).as_string().unwrap(),
&arr.get(3).as_string().unwrap(),
]
]

However, you may be working with vectors and have JS pass an array of unknown length at compile time, and you still have to build a nested array where every element is an array that contains two elements.

I’ve decided to implement this algorithm manually since it is quite simple to write:

The above code loops of our my_arr array, and pushes a new array of two elements into the my_arr_vec vector every two steps.

This means that our loop will work as long as we receive an array with an even number as length (since we want to build a vector with arrays of two elements inside).



News Credit

%d bloggers like this: