ベスパリブ

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

文字の並びに「リトルエンディアン」と言われても

まえがき

トルエンディアンとは、「LSBを先頭に持ってくる」ことだと理解している。

「先頭」とは、アドレスやレジスタやインデックスやキューの先頭のことである。アドレス値が若いほうが先頭か?それを先に決めてくれ。とにかく先頭の定義がまずあって、LSBを先頭に持ってくる。私はそう理解している。違ってたらごめん。

たとえば、「10進数の整数を与えたら2進数のビットに変換したものをリトルエンディアンのリストで返す」という関数 convert_little_endian_bits() を考える。そのとき、

a = convert_little_endian_bits(4)
# a = [0, 0, 1]

となる。リストの先頭 a[0] がLSBになっている。

本題

Q#では、リトルエンディアンであり、4=001であるという

このドキュメントでは、リトルエンディアン形式を使用して計算基底にラベルを付けています。 リトルエンディアン形式では、最下位ビットが先頭になります。 たとえば、リトルエンディアン形式の数値 4 は、ビット 001 の文字列で表されます。

(英語版) In this document we are using the little-endian format to label the computational basis. In little endian format, the least significant bits come first. For example, the number four in little-endian format is represented by the string of bits 001.

Qiskitでは、左が上位、右が下位での並びをリトルエンディアンと呼んでいる。つまり、4=100ということになる。(Qiskitのqubitの並びは  |q_n, \dots, q_0⟩である。)

Qiskitでは少し異なる量子ビットの順番をとります。左に最上位ビット(most significant bit、MSB)をとり、右に最下位ビット(least significant bit、LSB)をとるように表されます(リトルエンディアン)。これは古典コンピュータにおけるビット列表現と似ていて、測定が行われた後のビット列から整数への変換が容易になります。

(英語版) Qiskit uses a slightly different ordering of the qubits, in which the qubits are represented from the most significant bit (MSB) on the left to the least significant bit (LSB) on the right (little-endian). This is similar to bitstring representation on classical computers, and enables easy conversion from bitstrings to integers after measurements are performed.

Q#とQiskitは同じリトルエンディアン呼びをしていながら、4をビット列にしたときの並びが異なっている。

果たして、どちらが正しいのだろうか。

結局、ややこしくしているのは「先頭」の考えである。Q#では、配列的な並びだと左側が先頭(文字列 "001" を見たとき、当然インデックスは左から始まるので、先頭は左側だと考える)のに対して、Qiskitでは、古典ビットの並びだと右側が先頭(ビット列 100 を見たとき、当然0ビット目は右側なので、先頭は右側だと考える)になる。

このように意味が一意に定まらないので、文字の並びでトルエンディアンという呼び方をするのは不適格であると考える。

では代わりにどういう言い方がよいか。「左が上位ビットで、右が下位ビット」という言い方が誤解の仕様がなくてよいと思う。

AOJ0366 Road Improvement(道路網改修) の解説

問題:Road Improvement(道路網改修)

解説

「強連結成分分解(SCC)を使って同じグループの頂点を1つにまとめると、サイクルを含まないDAGになる」という性質を使う。

同じグループのものは1つの頂点とみなした新しいDAGグラフにおいて、入次数が0の頂点を根と呼び、出次数が0の頂点を葉と呼ぶことにする。

考察として、葉から根に有向辺を張れば、大きなループができてたくさんの道を通れるようになりそうである。

葉の数のほうが多い場合は葉の数だけ追加する必要があるし、根の数のほうが多い場合は、根の数だけ追加する必要がある。

なので、任意の頂点から全ての道を通れるために追加する道の数は、max(根の数, 葉の数)

ただし、グループ数が1のときは、すでに任意の都市からすべての道を通れるので、答えは0。

SCC後のDAGの根の判定

SCC後のDAGの根の判定は、グループ外からグループ内に有向辺が存在しないなら根。

グループ外からグループ内に有向辺があるかを調べるためには、グラフGの有向辺の向きを反転した、反転グラフinvGを用意すればよい。

SCC後のDAGの葉の判定

SCC後のDAGの葉の判定は、グループ内からグループ外に有向辺が存在しないなら葉。

実装

C++での解法です。

#define _USE_MATH_DEFINES  // M_PI等のフラグ
#include <bits/stdc++.h>
#define MOD 1000000007
#define COUNTOF(array) (sizeof(array)/sizeof(array[0]))
#define rep(i,n) for (int i = 0; i < (n); ++i)
#define intceil(a,b) ((a+(b-1))/b)
using namespace std;
using ll = long long;
using pii = pair<int,int>;
using pll = pair<long,long>;
const long long INF = LONG_LONG_MAX - 1001001001001001;
void chmax(int& x, int y) { x = max(x,y); }
void chmin(int& x, int y) { x = min(x,y); }

/** 強連結成分分解(SCC)
 * @brief
 * 有向グラフに対して「お互いに行き来できる頂点を同じグループにする」ことを強連結分解(SCC)という。
 *
 * サイクルをグループ分けするイメージ。
 *
 * @tparam T int, long long
 *
 * @param N 頂点数
 * @param G グラフG。G[i] := 頂点iに隣接している頂点集合
 * @example
 * Usage
 *   long long N = 4;
 *   long long M = 7;  // 辺数(使ってない)
 *   vector<set<long long>> graph(N);
 *   graph[0].insert(1);
 *   graph[1].insert(0);
 *   graph[1].insert(2);
 *   graph[3].insert(2);
 *   graph[3].insert(0);
 *   graph[0].insert(3);
 *   graph[1].insert(2);
 *
 *   // SCC実行
 *   SCC scc = SCC<long long>(N, graph);
 *   set<set<long long>> scc_groups = scc.scc_groups();
 *
 *   // SCCを見る
 *   scc.print_scc_groups(scc_groups);
 *   // group0 (size: 3): 0 1 3
 *   // group1 (size: 1): 2
 */
template<typename T>
class SCC {
    private:
        T N;  // 頂点数
        vector<set<T>> graph;  // graph[u] := 頂点uに隣接している頂点vの集合(uからvへの有向辺)
        vector<set<T>> rev_graph;  // graphの有向辺を反転させたグラフrev_graph
        vector<T> id2sccid;  // 強連結成分(SCC)用に記録する番号(頂点番号→SCCID)
        vector<T> sccid2id;  // SCCIDから頂点番号を割り出すテーブル(SCCID→頂点番号)

        /**
         * @brief
         * graphの有向辺を反転させたグラフrev_graphを確定させる。
         * O(頂点数+辺数)。
         */
        void fix_rev_graph() {
            this->rev_graph.assign(this->N, set<T>());
            for(T u=0; u<this->N; u++) {
                for(auto v: graph[u]) {
                    this->rev_graph[v].insert(u);
                }
            }
        }

        void dfs_step1(T u, T &sccid, set<T> &visited) {
            if (visited.count(u)) return;
            if (id2sccid[u]!=-1) return;
            visited.insert(u);

            for(auto v: this->graph[u]) {
                dfs_step1(v, sccid, visited);
            }

            id2sccid[u] = sccid;
            sccid2id[sccid] = u;
            sccid++;
        }

        void dfs_step2(T u, set<T> &visited, vector<bool> &step2_done) {
            if (visited.count(u)) return;
            if (step2_done[id2sccid[u]]) return;
            visited.insert(u);
            step2_done[id2sccid[u]] = true;

            for(auto v: this->rev_graph[u]) {
                dfs_step2(v, visited, step2_done);
            }
        }


    public:
        SCC(T N, vector<set<T>> graph) {
            this->N = N;
            this->graph = graph;
            this->id2sccid.assign(N, -1);
            this->sccid2id.assign(N, -1);
        }

        /**
         * @brief SCCを実行。
         * 計算量O(頂点数+辺数)。
         * @return set<set<T>> 強連結成分のグループ
         */
        set<set<T>> scc_groups() {
            // [ステップ1]
            // DFSの帰りがけ順に番号を振る
            T sccid = 0; // SCCでつける番号
            for(int u=0; u<N; u++) {
                set<T> visited;
                this->dfs_step1(u, sccid, visited);
            }

            // [ステップ2]
            // 辺の向きをすべて反転させ、番号の大きい順からDFSする
            // (実装のコツ)SCC用の番号i=N-1から順に、「反転させた有向辺が張っているならグループ化」をDFSでやっていく
            this->fix_rev_graph();
            vector<bool> step2_done(this->N, false);  // ステップ2で終了したSCCIDを記録する
            set<set<T>> scc_groups;  // 強連結成分のグループ
            for(int i=N-1; i>=0; i--) {
                if (step2_done[i]) continue;
                set<T> visited;
                dfs_step2(sccid2id[i], visited, step2_done);
                scc_groups.insert(visited);
            }

            return scc_groups;
        }

        /**
         * @brief scc_groupsの中身を見る。
         * @param scc_groups
         * @param idx_plus 頂点番号に足す数。デフォルト0。
         */
        void print_scc_groups(set<set<T>> &scc_groups, T idx_plus=0) {
            auto itr = scc_groups.begin();
            for(size_t i=0; i<scc_groups.size(); i++) {
                cout << "group" << i << " (size: " << (*itr).size() << "): ";
                for(auto u: *itr) {
                    cout << u+idx_plus << " ";
                }
                cout << endl;
                itr++;
            }
        }
};


void solve() {
    ll N, M; cin >> N >> M;
    vector<set<ll>> G(N);
    vector<set<ll>> invG(N);  // 反転グラフ
    for(ll i=0; i<M; i++) {
        ll s, t; cin >> s >> t;
        G[s].insert(t);
        invG[t].insert(s);
    }

    // SCCでグループを得る
    SCC<ll> scc(N, G);
    auto scc_groups = scc.scc_groups();
    if (scc_groups.size() == 1) { cout << 0 << endl; return; }

    // SCC後のDAGの根の判定は、グループ外からグループ内に有向辺が存在しないなら根
    ll root_num = 0;  // 根の数
    for(auto group: scc_groups) {
        bool is_root = true;
        for(ll u: group) {
            for(ll v: invG[u]) {
                if (!group.count(v)) { is_root = false; break; }
            }
            if (!is_root) { break; }
        }
        if (is_root) { root_num++; }
    }

    // SCC後のDAGの葉の判定は、グループ内からグループ外に有向辺が存在しないなら葉
    ll leaf_num = 0;  // 葉の数
    for(auto group: scc_groups) {
        bool is_leaf = true;
        for(ll u: group) {
            for(ll v: G[u]) {
                if (!group.count(v)) { is_leaf = false; break; }
            }
            if (!is_leaf) { break; }
        }
        if (is_leaf) { leaf_num++; }
    }

    cout << (ll)max(root_num, leaf_num) << endl;
}


int main() {
    solve();
    return 0;
}

Advertisement: 宣伝(日本情報オリンピック2009)の解説

問題:advertisement - 宣伝 (Advertisement)

解説

「強連結成分分解(SCC)を使って同じグループの頂点を1つにまとめると、サイクルを含まないDAGになる」という性質を使う。

連絡先を知っている関係をグラフ Gとして表す。  GをSCCしたあと、グループを1つの頂点とみなす。 求める答えは、入次数が0の頂点(根と呼ぶことにする)の数。

もとの Gの入次数をindeg[i] := 頂点iの入次数とする。 SCCするとグループがわかるので、グループ内同士の有向辺を取り除いて入次数を減らし、グループ内のすべての頂点の入次数が0になれば、そのグループは根となる。

SCCのグループは1つの頂点とみなすと、入次数0の頂点を数えればよい

実装

C++での解法です。

#define _USE_MATH_DEFINES  // M_PI等のフラグ
#include <bits/stdc++.h>
#define MOD 1000000007
#define COUNTOF(array) (sizeof(array)/sizeof(array[0]))
#define rep(i,n) for (int i = 0; i < (n); ++i)
#define intceil(a,b) ((a+(b-1))/b)
using namespace std;
using ll = long long;
using pii = pair<int,int>;
using pll = pair<long,long>;
const long long INF = LONG_LONG_MAX - 1001001001001001;
void chmax(int& x, int y) { x = max(x,y); }
void chmin(int& x, int y) { x = min(x,y); }

class SCC {
    private:
        long long N;  // 頂点数
        vector<set<long long>> graph;  // graph[u] := 頂点uに隣接している頂点vの集合(uからvへの有向辺)
        vector<set<long long>> rev_graph;  // graphの有向辺を反転させたグラフrev_graph
        vector<long long> id2sccid;  // 強連結成分(SCC)用に記録する番号(頂点番号→SCCID)
        vector<long long> sccid2id;  // SCCIDから頂点番号を割り出すテーブル(SCCID→頂点番号)

        void fix_rev_graph() {
            /*** graphの有向辺を反転させたグラフrev_graphを確定させる
             * O(頂点数+辺数)
             ***/
            this->rev_graph.assign(this->N, set<long long>());
            for(long long u=0; u<this->N; u++) {
                for(auto v: graph[u]) {
                    this->rev_graph[v].insert(u);
                }
            }
        }

        void dfs_step1(long long u, long long &sccid, set<long long> &visited) {
            if (visited.count(u)) return;
            if (id2sccid[u]!=-1) return;
            visited.insert(u);

            for(auto v: this->graph[u]) {
                dfs_step1(v, sccid, visited);
            }

            id2sccid[u] = sccid;
            sccid2id[sccid] = u;
            sccid++;
        }

        void dfs_step2(long long u, set<long long> &visited, vector<bool> &step2_done) {
            if (visited.count(u)) return;
            if (step2_done[id2sccid[u]]) return;
            visited.insert(u);
            step2_done[id2sccid[u]] = true;

            for(auto v: this->rev_graph[u]) {
                dfs_step2(v, visited, step2_done);
            }
        }


    public:
        SCC(long long N, vector<set<long long>> graph) {
            this->N = N;
            this->graph = graph;
            this->id2sccid.assign(N, -1);
            this->sccid2id.assign(N, -1);
        }

        set<set<long long>> scc_groups() {
            /* SCCを実行
            Return:
                scc_groups(set<set<long long>>): 強連結成分のグループ
            Notes:
                計算量O(頂点数+辺数)
            */

            // [ステップ1]
            // DFSの帰りがけ順に番号を振る
            long long sccid = 0; // SCCでつける番号
            for(int u=0; u<N; u++) {
                set<long long> visited;
                this->dfs_step1(u, sccid, visited);
            }

            // [ステップ2]
            // 辺の向きをすべて反転させ、番号の大きい順からDFSする
            // (実装のコツ)SCC用の番号i=N-1から順に、「反転させた有向辺が張っているならグループ化」をDFSでやっていく
            this->fix_rev_graph();
            vector<bool> step2_done(this->N, false);  // ステップ2で終了したSCCIDを記録する
            set<set<long long>> scc_groups;  // 強連結成分のグループ
            for(int i=N-1; i>=0; i--) {
                if (step2_done[i]) continue;
                set<long long> visited;
                dfs_step2(sccid2id[i], visited, step2_done);
                scc_groups.insert(visited);
            }

            return scc_groups;
        }

        void print_scc_groups(set<set<long long>> &scc_groups) {
            /* scc_groupsの中身を見る */
            auto itr = scc_groups.begin();
            for(int i=0; i<scc_groups.size(); i++) {
                cout << "group" << i << " (size: " << (*itr).size() << "): ";
                for(auto u: *itr) {
                    cout << u << " ";
                }
                cout << endl;
                itr++;
            }
        }
};


void solve() {
    ll N, M; cin >> N >> M;
    vector<set<ll>> G(N);
    vector<ll> indeg(N, 0);  // 入次数
    for(ll i=0; i<M; i++) {
        ll a, b; cin >> a >> b; a--; b--;
        G[a].insert(b);
        indeg[b]++;
    }

    // 強連結成分分解(SCC)を使って同じグループの頂点を一つにまとめると、サイクルを含まないDAGになる
    // DAGの根(入次数0の頂点)の個数が答え。
    SCC scc(N, G);
    auto scc_groups = scc.scc_groups();

    ll ans = 0;
    // SCCのグループは1つの頂点とみなす
    for(auto group: scc_groups) {
        // グループ内同士の有向辺ぶんだけ入次数をへらす
        for(auto u: group) {
            for(auto v: G[u]) {
                if (!group.count(v)) { continue; }
                indeg[v]--;
            }
        }
        // グループ内のすべての頂点の入次数が0なら根となる
        bool is_root = true;
        for(auto u: group) {
            if (indeg[u] != 0) { is_root = false; break; }
        }
        if (is_root) { ans++; }
    }
    cout << ans << endl;
}


int main() {
    solve();
    return 0;
}

完璧な焼き鳥体験はない。完璧な人間がいないようにね。

焼き鳥を食べに、焼き鳥屋に行った。

焼き鳥が食べたくてしょうがなかったので、焼鳥屋に行った形となる。

私は今日は焼き鳥を阿呆ほど食う所存。全品制覇する構えだ。

その意気込みで友人と焼き鳥屋に着いた形となった。

とりあえず生ビールと、スピードメニューとしてもつ煮込み、串キャベツ、冷やしトマトを頼んだ。同時に焼き鳥を、かわ、つくね、ねぎま、レバー、ハツを頼んだ。

焼き野菜も食べようと、しいたけとプチトマトを注文した。注文したあとでトマトが被ったことに気づいた。

最初は野菜から摂ると良い。そんなことを考えながらキャベツと冷やしトマトをつまみ、もつ煮込みもつつきながらちびちび飲んでいると、腹が半分膨れていることに気づく。私は焼鳥を食べに来たんじゃないのか?

そして満を持して焼き鳥が到着した。

人と話しながらの食事は、食べるのが遅くなる。かわが好きなのでかわをまず食べる。次につくねが好きなのでつくねを食べる。次にねぎまが好きなのでねぎまのもも肉とねぎをかじったあと、レバーとハツにも手をつけようとするが、すでに冷めている。一度に注文し過ぎか、食うのが遅いからか。

私はまだ3串しか焼き鳥を食べていないのだが、すでに食事は終盤に差し掛かっていると思った。

もつ煮込みはまだいい。キャベツとしいたけなんて家で食えるじゃないか。プチトマトなんて別に好きじゃないじゃないか。なんで注文したんだ。

最後に豚バラを追加で注文する。腹は膨れていたが、まだ焼き鳥を食べなければという強迫観念、または惰性であった。

せせり、砂肝、なんこつ、ささみ、最初にメニューを見て食べようと思っていた焼き鳥たちは、ついぞ食べることはなく店を出た。

全品制覇するんじゃなかったのか?

一週間経ってもモヤモヤは晴れなかった。私は焼き鳥を食べるつもりで焼き鳥屋に行ったのに、焼き鳥をほとんど食べずに焼き鳥屋を出てしまったことを後悔していた。

リベンジとして、コンビニで焼き鳥を大量に買って帰ることにした。

かわともも肉を塩とタレで2本ずつ、つくね、お母さん食堂の炭火焼鳥、レバー。なんこつ唐揚げを買おうとしたが、焼鳥じゃないので自分を窘めた。私は焼き鳥を食うんだよ。缶ビールとハイボールを買って帰る。コンビニは最強であった。

家でアマプラとYouTubeを観ながら、焼き鳥を食べた。

焼き鳥は美味しかった。好きなアニメと配信を観ながら食べるのも良かった。しかし、ああ違ったのだなとも思った。私はあの日、ひさしぶりに会う友人と、ひさしぶりに食べる焼き鳥を楽しみにしていたのだなと思った。友人との会話は楽しかったが、焼き鳥という欠けたピースは、今日焼き鳥を食べてもあの日の埋め合わせは不可能なのだなと気づいた。

得難い機会を逸したのだと気づいた。

私は人間である。完璧な焼き鳥体験は、まだない。

'One or more instructions cannot be converted to a gate. "barrier" is not a gate instruction' エラー

環境

  • qiskit-terra 0.20.1
  • qiskit 0.36.1
  • Python version 3.10.4

本題

以下のコードを実行すると、表題のエラーが発生します。

from qiskit import QuantumCircuit

qc = QuantumCircuit(1)
qc.x(0)
qc.barrier()
gate = qc.to_gate()
---------------------------------------------------------------------------
QiskitError                               Traceback (most recent call last)
      4 qc.x(0)
      5 qc.barrier()
----> 6 gate = qc.to_gate()
...(略)
QiskitError: 'One or more instructions cannot be converted to a gate. "barrier" is not a gate instruction'

このエラーは回路にto_gate()で変換することができない命令がある場合に発生します。今回の場合、barrier()のせいで発生しています。

barrier()は回路の見た目をきれいにするためだけのもので、回路の本質ではないので削除してOKです。

以下のコードで削除することができます。

from qiskit.transpiler.passes import RemoveBarriers

new_qc = RemoveBarriers()(qc)

これでエラーが発生しなくなります。

from qiskit import QuantumCircuit

qc = QuantumCircuit(1)
qc.x(0)
qc.barrier()

# 境界を削除してゲート化
new_qc = RemoveBarriers()(qc)
gate = new_qc.to_gate()

参考URL

Uncaught (in promise) TypeError: hoge is not a function エラー

webpack等を使用したTypeScript環境で 、wasm-pack build で生成された pkg を import してwasmの関数を使おうとしても、なぜかis not a function というエラーが出てしまう。

以下では、oxgame.make_initialized_grid というwasmの関数を呼び出しているが、「oxgame.make_initialized_grid is not a function」というエラーが発生してしまっている。

import * as oxgame from "../pkg/oxgame";

const entry_point = async () => {
    console.log("entry_point");

    let grid = oxgame.make_initialized_grid();
    console.log(grid);
}

window.onload = () => {
    entry_point();
}

f:id:takeg:20220208221400p:plain
consoleに出力されたエラー内容

TypeScriptを使わない生のJavaScript環境ではwasmの関数を呼び出せているので、webpackでトランスパイルするときに関数の実体が紐付かれてなさそう?とにかくwebpackの設定が悪いだろうとあたりを付ける。

ごちゃごちゃした結果、webpack.config.jstsconfig.jsonの2つの設定を追加すると正しく呼び出せるようになった。

webpack.config.jsasyncWebAssembly: true を追加

// webpack.config.js
module.exports = {
    ...
    experiments: {
        asyncWebAssembly: true,
    },
}

参考:Experiments | webpack

tsconfig.jsonmoduleESNext にする

{
  "compilerOptions": {
    "module": "ESNext", //"commonjs", 
    ...
  }
}

参考:Typescript + WebAssembly(Rust)の環境設定 - Qiita

すると

import * as oxgame from "../pkg/oxgame";

const entry_point = async () => {
    console.log("entry_point");

    let grid = oxgame.make_initialized_grid();
    console.log(grid);
}

/* module を ESNext にしたら window.onload が呼ばれなくなったよ */
// window.onload = () => {
//     entry_point();
// }
entry_point();

f:id:takeg:20220208221012p:plain
エラーは消えてちゃんと動いている

wasmの関数呼び出しに成功しました。

正直よくわかってないです。

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