ベスパリブ

プログラミングを主とした日記・備忘録です。ベスパ持ってないです。

Rust の wasm-pack のつまりどころ

環境

  • 記事作成時: 2022/01/27
  • Windows10
  • WSL2
  • rustc 1.58.1 (db9d1b20b 2022-01-20)
  • cargo 1.58.0 (f01b232bc 2022-01-19)

can't find library hoge, rename file to src/lib.rs or specify lib.path

wasm-pack build をしたときに以下のようなエラーが発生しました。

$ wasm-pack build
Error: Error during execution of `cargo metadata`: error: failed to parse manifest at `/mnt/c/Users/takeg/Desktop/myspace/workspace/mywasm/Cargo.toml`

Caused by:
  can't find library `mywasm`, rename file to `src/lib.rs` or specify lib.path

デフォルトでは wasm-pack build のターゲットは src/lib.rs になっているので、そのようにファイル名をリネームするといいです。

lib.rsにリネームしたくなかったら、ターゲットのファイル名を Cargo.tomllib.path に指定するといいです。

たとえば src/mywasm.rs というファイルを wasm-pack build のターゲットにしたかったら、以下のように Cargo.toml[lib] セクションの path に追加します。

# Cargo.toml
[package]
略

[lib]
path = "src/mywasm.rs"

error: the wasm32-unknown-unknown target is not supported by default

以下のようなエラーが発生しました。

$ wasm-pack build
[INFO]: Checking for the Wasm target...
[INFO]: Compiling to Wasm...
   Compiling proc-macro2 v1.0.36
   Compiling syn v1.0.86
   Compiling wasm-bindgen-shared v0.2.79
   Compiling log v0.4.14
   Compiling getrandom v0.2.4
   Compiling wasm-bindgen v0.2.79
error: the wasm32-unknown-unknown target is not supported by default, you may need to enable the "js" feature. For more information see: https://docs.rs/getrandom/#webassembly-support
   --> /home/taketakeyyy/.cargo/registry/src/github.com-1ecc6299db9ec823/getrandom-0.2.4/src/lib.rs:225:9
    |
225 | /         compile_error!("the wasm32-unknown-unknown target is not supported by \
226 | |                         default, you may need to enable the \"js\" feature. \
227 | |                         For more information see: \
228 | |                         https://docs.rs/getrandom/#webassembly-support");
    | |________________________________________________________________________^

error[E0433]: failed to resolve: use of undeclared crate or module `imp`
   --> /home/taketakeyyy/.cargo/registry/src/github.com-1ecc6299db9ec823/getrandom-0.2.4/src/lib.rs:252:5
    |
252 |     imp::getrandom_inner(dest)
    |     ^^^ use of undeclared crate or module `imp`

For more information about this error, try `rustc --explain E0433`.
error: could not compile `getrandom` due to 2 previous errors
warning: build failed, waiting for other jobs to finish...
error: build failed
Error: Compiling your crate to WebAssembly failed
Caused by: failed to execute `cargo build`: exited with exit status: 101
  full command: "cargo" "build" "--lib" "--release" "--target" "wasm32-unknown-unknown"

エラーメッセージ中の For more information see のリンク先を参照し、Cargo.tomlgetrandom = { version = "0.2", features = ["js"] } を追加したらエラーは消えました。

# Cargo.toml
[dependencies]
getrandom = { version = "0.2", features = ["js"] }

error: const definitions aren't supported with #[wasm_bindgen]

以下のようなエラーが発生しました。

$ wasm-pack build
[INFO]: Checking for the Wasm target...
[INFO]: Compiling to Wasm...
   Compiling mywasm v0.1.0 (/mnt/c/Users/takeg/Desktop/myspace/workspace/mywasm)
error: const definitions aren't supported with #[wasm_bindgen]
  --> src/mywasm.rs:10:5
   |
10 |     pub const WIN: i32 = 10;
   |     ^^^^^^^^^^^^^^^^^^^^^^^^

#[wasm_bindgen]const には対応していないらしいので、該当の#[wasm_bindgen]を削除します。

ちなみに以下のようにしていました。

/* 評価値の定数 */
#[wasm_bindgen]
pub struct Eval {}
#[wasm_bindgen]  // これが不要
impl Eval {
    pub const WIN: i32 = 10;
    pub const LOSE: i32 = -10;
    pub const DRAW: i32 = 0;
}

the trait FromWasmAbi is not implemented for hoge

以下のようなエラーが発生しました。

$ wasm-pack build
[INFO]: Checking for the Wasm target...
[INFO]: Compiling to Wasm...
   Compiling mywasm v0.1.0 (/mnt/c/Users/takeg/Desktop/myspace/workspace/wasm-test/)
error[E0277]: the trait bound `Node: FromWasmAbi` is not satisfied
  --> src/lib.rs:45:1
   |
45 | #[wasm_bindgen]
   | ^^^^^^^^^^^^^^^ the trait `FromWasmAbi` is not implemented for `Node`
   |
   = note: this error originates in the attribute macro `wasm_bindgen` (in Nightly builds, run with -Z macro-backtrace for more info)

たとえば the trait `FromWasmAbi` is not implemented for `Node` とあったら、自分で作ったRustコード中の Node#[wasm_bindgen] をつけ忘れています。

#[wasm_bindgen]  // これをつけ忘れています
pub struct Node {
    pub eval: i32,
    pub h: usize,
    pub w: usize,
}

Rust で JavaScriptalertconsole.log を使いたい

を参考にして使えるようにしておくと、デバッグが楽になります。

以下では、Rustで console.log を以下のようにして呼び出しています。

console_log!("aiueo");

Vec<T> に対応していない(?)

wasmのRustではVec型をreturnできないAllow returning Vec の議論を見るとわかるように、Vec<T>をwasmで扱うにはコツがいります。というか、Rust <-> JavaScript間で型をやりとりすることにコツがいります。

とりあえず Supported Types | The wasm-bindgen Guide を見るといいかもしれません。

色々調べた感じ、主な解決方法としては JSON形式でRust <-> JavaScrit間をやりとりすることっぽいです。これぞ JavaScript って感じがしますね。この方法は後述する"serde-serialize" Feature を使うことで簡単に実現できます。

このあたりの型のやりとりに関しては The wasm-bindgen Guideチュートリアルを進めつつ、自分のやりたいことに寄せて改造していくと勘所をつかむことができると思います。

Vec<i32> を返すには?

普通に返すことができます。ただ、引数に &Vec<i32>&mut Vec<i32> は使うことはできません。

use wasm_bindgen::prelude::*;

// 適当に Vec<i32> を作成して返す関数
#[wasm_bindgen]
pub fn return_vec_i32() -> Vec<i32> {
    let v = vec![11,22,33];
    v
}

// 値 を console.log を使って表示する関数
#[wasm_bindgen]
pub fn print_vec_i32(v: Vec<i32>) {  // &Vec<i32> や &mut Vec<i32> はだめ
    for i in 0..v.len() {
        console_log!("v[{}]: {}", i, v[i]);
    }
}

JavaScript側のコードは以下になります。

import * as wasm from "mywasm";

{
    let v = wasm.return_vec_i32();
    wasm.print_vec_i32(v);
    wasm.print_vec_i32(v);  // JavaScrit側の呼び出しでは、所有権のムーブの概念はないので、2回呼び出してもOK
}

console.log で、以下のように表示されます。

v[0]: 11
v[1]: 22
v[2]: 33
v[0]: 11
v[1]: 22
v[2]: 33

Vec<String> を返すには?

たとえば以下のような Vec<String> を返すような関数 return_vec_string を作成したとします。

// 適当に Vec<String> を作成して返す関数
#[wasm_bindgen]
pub fn return_vec_string() -> Vec<String> {
    let v = vec!["aaa", "bbb"];
    v
}

これをコンパイルしようとしても、エラーが発生します。

$ wasm-pack build
[INFO]: Checking for the Wasm target...
[INFO]: Compiling to Wasm...
   Compiling mywasm v0.1.0 (/mnt/c/Users/takeg/Desktop/myspace/workspace/mywasm/)
error[E0277]: the trait bound `std::string::String: JsObject` is not satisfied
  --> src/lib.rs:92:1
   |
92 | #[wasm_bindgen]
   | ^^^^^^^^^^^^^^^ the trait `JsObject` is not implemented for `std::string::String`
   |
   = note: required because of the requirements on the impl of `IntoWasmAbi` for `Box<[std::string::String]>`
   = note: 1 redundant requirement hidden
   = note: required because of the requirements on the impl of `IntoWasmAbi` for `Vec<std::string::String>`
   = note: required because of the requirements on the impl of `ReturnWasmAbi` for `Vec<std::string::String>`
   = note: this error originates in the attribute macro `wasm_bindgen` (in Nightly builds, run with -Z macro-backtrace for more info)
...

こういうときは、Box<[JsValue]> を使うのが一つの手です。これを使えば、以下のように書くことができます。

// 適当に Vec<String> を作成して返す関数
#[wasm_bindgen]
pub fn return_vec_string() -> Box<[JsValue]> {
    let v = vec![JsValue::from_str("aaa"), JsValue::from_str("bbb")];
    v.into_boxed_slice()
}

// 値 を console.log を使って表示する関数
#[wasm_bindgen]
pub fn print_vec_string(v: Box<[JsValue]>) {
    for i in 0..v.len() {
        let s = format!("v[{}]: {:?}", i, JsValue::as_string(&v[i]));
        console_log!("{}", &s);
    }
}

JavaScript 側では以下のように呼び出します。

import * as wasm from "mywasm";

{
    let v = wasm.return_vec_string();
    wasm.print_vec_string(v);
}

すると、以下のように表示されます。

v[0]: Some("aaa")
v[1]: Some("bbb")

余計な Some が入ってしまっています。これは JsValue::as_string(&v[i]) の返り値が Option<T> だからでしょう。

Rustのコード側で Some をとるには、以下のようにやります。

// 値 を console.log を使って表示する関数
#[wasm_bindgen]
pub fn print_vec_string(v: Box<[JsValue]>) {
    for i in 0..v.len() {
        match JsValue::as_string(&v[i]) {
            None => {},
            Some(s) => {
                console_log!("v[{}]: {}", i, &s);
            }
        }
    }
}

すると以下のように表示されます。

v[0]: aaa
v[1]: bbb

Vec<Vec<i32>> を返すには?

複雑な型になると考えるのがしんどくなります。そこで "serde-serialize" Feature を使うとほとんどの型で解決できます。

Cargo.tomlserde-serialize を使うための設定を追加します。

# Cargo.toml
[dependencies]
# wasm-bindgen = "0.2.63"
wasm-bindgen = { version = "0.2.63", features = ["serde-serialize"] }
serde_json = "1.0"
serde_derive = "1.0"
serde = { version = "1.0", features = ["derive"] }

Rustのコードは以下になります。

use wasm_bindgen::prelude::*;

// 適当に Vec<Vec<i32>> を作成して返す関数
#[wasm_bindgen]
pub fn return_vec_vec_i32() -> JsValue{
    let mut res_v: Vec<Vec<i32>> = vec![];
    for _ in 0..3 {
        let v = vec![1,2,3];
        res_v.push(v);
    }

    return JsValue::from_serde(&res_v).unwrap();
}

// 値 を console.log を使って表示する関数
#[wasm_bindgen]
pub fn print_vec_vec_i32(val: &JsValue) {
    let v: Vec<Vec<i32>> = val.into_serde().unwrap();
    for i in 0..v.len() {
        for j in 0..v[i].len() {
            console_log!("v[{}][{}]: {}", i, j, v[i][j]);
        }
    }
}

JavaScript側では以下のようにして呼び出します。

import * as wasm from "mywasm";

{
    let v = wasm.return_vec_vec_i32();
    wasm.print_vec_vec_i32(v);
}

すると、以下のように表示されます。

v[0][0]: 1
v[0][1]: 2
v[0][2]: 3
v[1][0]: 1
v[1][1]: 2
v[1][2]: 3
v[2][0]: 1
v[2][1]: 2
v[2][2]: 3

自作のstructを返すには?

これも serde-serialize を使えばOKです。

Rustのコード側では以下のようにします。

use wasm_bindgen::prelude::*;
use serde::{Serialize, Deserialize};


// Node の定義
#[derive(Default)]
#[derive(Serialize, Deserialize)]
#[wasm_bindgen]
pub struct Node {
    pub eval: i32,  // 評価値
    pub h: usize,   // h座標
    pub w: usize,   // w座標
}

// 適当にNodeを作成して返す関数
#[wasm_bindgen]
pub fn return_node() -> JsValue {
    let node: Node = Node{eval: 100, h: 1, w: 2};
    return JsValue::from_serde(&node).unwrap();
}

// Node を alert を使って表示する関数
#[wasm_bindgen]
pub fn print_node(val: &JsValue) {
    let node: Node = val.into_serde().unwrap();
    let mut s = format!("node.eval: {}", node.eval);
    alert(&s);  // node.evalを表示
    s = format!("node.h: {}", node.h);
    alert(&s);  // node.hを表示
    s = format!("node.w: {}", node.w);
    alert(&s);  // node.wを表示
}

JavaScript側では以下のようにして呼び出します。

import * as wasm from "mywasm";

{
    let node = wasm.return_node();
    wasm.print_node(node);
}

すると以下のようにalert()が呼び出されます。

f:id:takeg:20220128222145p:plain