🍣

wasm-bindgen: Javascriptから呼び出せるRust関数を作ろう

に公開

wasm-bindgen: Javascriptから呼び出せるRust関数を作ろう

はじめに

Rustは高速な実行速度を持つプログラミング言語であり、WebAssembly(Wasm)を使うことで、ブラウザ上でもそのパフォーマンスを活かすことができる。具体的には、時系列データの統計処理などを高速に行うことが可能である。

wasm-bindgenは、Rustで書いた関数をJavaScriptから呼び出せるようにするためのツールである。今回は文字列を返す関数をRustで書き、それをReactアプリケーションから呼び出す方法を紹介する。

なお、RustとReactについての基本的な知識があることを前提にしている。

この記事で話すこと

  • wasm-bindgenの基本的な使い方
  • Rustで書いた関数をJavaScriptから呼び出す方法
  • オブジェクトの扱い方

この記事で話さないこと

  • wasm-bindgen, Rust以外の詳細(Reactなど)

セットアップ

Viteのセットアップ

pnpm create vite@latest
  • プロジェクト名
    • react-wasm-helloworld
  • フレームワーク
    • React
  • TypeScriptを使用

wasm-packのインストール

cargo install wasm-pack

wasmのセットアップ

cargo new --lib wasm

hello worldを表示する

Rustで"Hello, World!"を返す関数を作成し、web-packを用いてWasmにコンパイルする。その後、Reactアプリケーションからこの関数を呼び出して結果を表示する。

wasm/src/lib.rsを書き換える

wasm/src/lib.rs
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub fn greet() -> String {
    "Hello, World!".to_string()
}

wasmのビルド

  • 設定
Cargo.toml
[package]
name = "wasm"
version = "0.1.0"
edition = "2024"

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

[dependencies]
wasm-bindgen = "0.2.100"
  • ビルド
cd wasm
wasm-pack build --target web

Reactからwasmを呼び出す

  • wasmをApp.tsxにインポート
src/App.tsx
import init, { greet } from '../wasm/pkg/wasm.js'
  • wasmの初期化
src/App.tsx
useEffect(() => {
  init()
}, [])
  • greet関数を呼び出して表示
src/App.tsx
const res = greet()
// Hello, World!
console.log(res)

RustとTS間でオブジェクトをやり取りする

Tsifyというクレートを使うことで、オブジェクトのやり取りが型安全で行える。TsifyはRustの構造体をTypeScriptの型に変換するためのツールであり、wasm-bindgenと組み合わせて使用することで、RustとTypeScript間でオブジェクトをやり取りできるようになる。

クレートのインストール

cargo add serde
cargo add tsify

オブジェクトの定義

wasm/src/types.rs
use serde::{Deserialize, Serialize};
use tsify::Tsify;

#[derive(Tsify, Serialize, Deserialize)]
#[tsify(into_wasm_abi, from_wasm_abi)]
pub struct GreetResponse {
    pub value: String,
    #[tsify(optional)]
    pub name: Option<String>,
}
  • Tsifyを使うことで、Rustの構造体からTypeScriptの型を生成できる
  • #[tsify(optional)]により、TypeScriptのオプショナルなプロパティを扱える

src/lib.rsの修正

wasm/src/lib.rs
mod types;

use types::GreetResponse;
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub fn greet(name: Option<String>) -> GreetResponse {
    GreetResponse {
        value: "Hello, World!".to_string(),
        name,
    }
}

Reactからの呼び出し

src/App.tsx
const res = greet()
const value = res.value
const name = res.name ?? ''
const message = `${value} ${name}`
// Hello, World!
console.log(message)
  • 引数なしで呼び出すとnameはnullになる
src/App.tsx
// const res = greet()
const res = greet('test')
const value = res.value
const name = res.name ?? ''
const message = `${value} ${name}`
// Hello, World! test
console.log(message)
  • 引数を渡すとnameに値が入る

ビルド時に生成されている型

  • wasm/pkg/wasm.d.tsにTypeScriptの型が生成されている
wasm/pkg/wasm.d.ts
export function greet(name?: string | null): GreetResponse;
export interface GreetResponse {
    value: string;
    name?: string;
}

ここで紹介したコード

https://github.com/k22036/react-wasm-helloworld

まとめ

  • wasm-bindgenを使うことで、Rustで書いた関数をJavaScriptから呼び出せるようになる
  • Tsifyを使うことで、RustとTypeScript間でオブジェクトのやり取りが型安全に行える
Emoba Tech Blog

Discussion