22

It's possible to pass an array of integers like this:

const js = import("./webassembly_rust");
let array_nums = [1,2,3,4,5,6,7,8,9];

js.then(js => {
  js.test( array_nums );
}); 

to WebAssembly and save it in a vector like this:

extern crate serde_json;
extern crate wasm_bindgen;

use wasm_bindgen::prelude::*;

#[macro_use]
extern crate serde_derive;

#[wasm_bindgen]
pub fn test(array: JsValue) {
    let elements: Vec<u32> = array.into_serde().unwrap();
}

It's also possible to pass a single object like this:

const js = import("./webassembly_rust");
let jsObject = {name: "hello world", id: "99", parent_id: "11"};

js.then(js => {
  js.test( jsObject );
}); 

to WebAssembly and save it as a Element struct like this:

extern crate serde_json;
extern crate wasm_bindgen;

use wasm_bindgen::prelude::*;

#[macro_use]
extern crate serde_derive;

#[derive(Serialize, Deserialize)]
pub struct Element {
    name: String,
    id: String,
    parent_id: String,
}

#[wasm_bindgen]
pub fn test(js_object: &JsValue) {
    let element: Element = js_object.into_serde().unwrap();
}

The next thing I tried is to pass an array of objects like this:

const js = import("./webassembly_rust");
let arrayOfObjects = [
  {name: "hello world", id: "99", parent_id: "88"},
  {name: "hello world2", id: "88", parent_id: "12"},
  {name: "hello world3", id: "77", parent_id: "88"}
]

js.then(js => {
  js.test( arrayOfObjects );
}); 

to WebAssembly and save it as a vector of Element structs like this:

extern crate serde_json;
extern crate wasm_bindgen;

use wasm_bindgen::prelude::*;

#[macro_use]
extern crate serde_derive;

#[derive(Serialize, Deserialize)]
pub struct Element {
    name: String,
    id: String,
    parent_id: String,
}

#[wasm_bindgen]
pub fn test(js_objects: &JsValue) {
    let elements: Vec<Element> = js_objects.into_serde().unwrap();
}

This compiles, but when I run this code I get the error:

func $__rust_start_panic (param i32) (result i32)
  unreachable
  unreachable
end

screenshot_promise_rejection_error

Passing an array of objects filled with numbers like this:

const js = import("./webassembly_rust");
let arrayOfNumObjects = [
    {name: 1, id: 2, parent_id: 3 },
    {name: 1, id: 2, parent_id: 3 },
    {name: 1, id: 2, parent_id: 3 }
]

js.then(js => {
  js.test( arrayOfNumObjects );
}); 

to WebAssembly is possible when the Element struct contains only u32 values.

extern crate serde_json;
extern crate wasm_bindgen;

use wasm_bindgen::prelude::*;

#[macro_use]
extern crate serde_derive;

#[derive(Serialize, Deserialize)]
pub struct Element {
    name: u32,
    id: u32,
    parent_id: u32,
}

#[wasm_bindgen]
pub fn test(js_objects: &JsValue) {
    let elements: Vec<Element> = js_objects.into_serde().unwrap();
}

It seems like the problem is caused by the String type in the Element struct.

What did I do wrong?

I found the following articles, but I can't find a solution for my problem in them:

  • Serializing and Deserializing Arbitrary Data Into and From JsValue with Serde

    This explains how to convert a JavaScript object to a struct, but not how to convert an array of objects to a vector of structs.

  • js_sys crate

    This crate allows to use JavaScript types like arrays or objects in Rust, but this is not what I want. I want to convert JavaScript values into their Rust counterparts. This crate only allows to use JavaScript inline in Rust, as far as I understand... and this isn't as fast as using just Rust.

  • github issue

Stargateur
  • 24,473
  • 8
  • 65
  • 91
Gregor
  • 331
  • 2
  • 6

1 Answers1

11

Follow the instructions to get a basic Rust / WASM setup, then add support for arbitrary data via Serde.

I've changed your code to return a number and print out that number, just to see that it's working.

Cargo.toml

[package]
name = "ww"
version = "0.1.0"
authors = ["An Devloper <an.devloper@example.com>"]
edition = "2018"

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

[dependencies]
wasm-bindgen = { version = "0.2", features = ["serde-serialize"] }
serde_json = "1.0.32"
serde_derive = "1.0.80"
serde = "1.0.80"

src/lib.rs

extern crate serde_json;
extern crate wasm_bindgen;

use wasm_bindgen::prelude::*;

#[macro_use]
extern crate serde_derive;

#[derive(Serialize, Deserialize)]
pub struct Element {
    name: String,
    id: String,
    parent_id: String,
}

#[wasm_bindgen]
pub fn test(js_objects: &JsValue) -> i32 {
    let elements: Vec<Element> = js_objects.into_serde().unwrap();
    elements
        .iter()
        .map(|e| {
            let id = e.id.parse::<i32>().unwrap_or(0);
            let parent_id = e.parent_id.parse::<i32>().unwrap_or(0);
            id + parent_id
        })
        .sum()
}

index.js

const js = import("./ww");

let arrayOfObjects = [
  { name: "hello world", id: "99", parent_id: "88" },
  { name: "hello world2", id: "88", parent_id: "12" },
  { name: "hello world3", id: "77", parent_id: "88" },
]

js.then(js => {
  const sum = js.test(arrayOfObjects);
  console.log(sum);
});

package.json

{
  "scripts": {
    "serve": "webpack-dev-server"
  },
  "devDependencies": {
    "html-webpack-plugin": "^3.2.0",
    "webpack": "^4.0.1",
    "webpack-cli": "^3.1.1",
    "webpack-dev-server": "^3.1.0"
  }
}

webpack.config.js

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  entry: "./index.js",
  output: {
    path: path.resolve(__dirname, "dist"),
    filename: "index.js",
  },
  plugins: [
    new HtmlWebpackPlugin({
      title: "Getting started with WASM"
    })
  ],
  mode: "development"
};

Then run:

# once
npm install
# every time the code changes
cargo +nightly build --target wasm32-unknown-unknown
wasm-bindgen target/wasm32-unknown-unknown/debug/*.wasm --out-dir .
npm run serve

Visit the page in your WASM-enabled browser.


Observant readers will note that I didn't do anything different from the OP. That's because this code already worked as-is. Every time you change your Rust code, make sure that you:

  1. Build your Rust code
  2. Re-run wasm-bindgen
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • 1
    Interestingly it worked in the meantime with my project too, although i don't know why. The only thing that was different is that i added the crate `console_error_panic_hook` to dig deeper into the problem. Before the error occured i always build my rust code with: `cargo +nightly build --target wasm32-unknown-unknown && wasm-bindgen target/wasm32-unknown-unknown/debug/webassembly_rust.wasm --out-dir . ` and run the project with `npm run serve`. I'm curious what caused the error in the first place, but thank you for your detailed post :) – Gregor Oct 15 '18 at 15:23
  • @Shepmaster I was under the impression that running `wasm-bindgen` also re-built the Rust source code? – jjpe Nov 02 '18 at 09:07
  • @jjpe it's possible that things have changed since I last checked, but the [documentation for wasm-bindgen](https://rustwasm.github.io/wasm-bindgen/whirlwind-tour/basic-usage.html) specifically requires the path to the output of the compiler, so I don't believe it builds the code for you. There are e.g. webpack loaders (such as [rust-native-wasm-loader](https://github.com/dflemstr/rust-native-wasm-loader), no idea if it's good) that presumably do rebuild for you. – Shepmaster Nov 02 '18 at 12:59