LoginSignup
4
3

More than 1 year has passed since last update.

wasm_bindgenでRust側のエラーをJavascript側でキャッチする

Posted at

今回は,wasm_bindgenでJavascriptからWasmを呼ぶときのエラーハンドリングをしてみます.具体的にはthiserrorで作成したエラーをJavascript側でキャッチします.wasm_bindgenでErrorの継承を行う方法が分からなかったので,エラー自体はJavascript側で定義しwasm_bindgenでRust側にインポートします.

簡単なものですがプログラムはこちらにあります.

エラーの定義

エラーは以下で定義しています.

error.rs
#[derive(thiserror::Error, Debug)]
#[error("too small x for log(x) x:{0}. (expected x > 0.01)")]
pub struct LogSmallError(pub f64);

#[derive(thiserror::Error, Debug)]
#[error("too large x for exp(x) x:{0}. (expected x < 10)")]
pub struct ExpLargeError(pub f64);

エラーをRust側にインポートする

Javascript側でエラーのクラスを定義します.ファイルはwasm_bindgenが読み込める場所ならどこにあっても大丈夫ですが,今回はpkgディレクトリの中に作成します.エラーメッセージのフォーマットなどはRust部分で定義しているので,ここでは必要最低限の定義だけしています.

rustのワークスペース/pkg/wasm_error.ts
export class MathError extends Error {
    constructor(...params: any[]) {
        super(...params)
        this.name = "MathError"
    }
}

export class LogSmallError extends MathError {
    constructor(...params: any[]) {
        super(...params)
        this.name = "LogSmallError"
    }
}

export class ExpLargeError extends MathError {
    constructor(...params: any[]) {
        super(...params)
        this.name = "ExpLargeError"
    }
}

wasm_bindgenではexten cの中にJavascriptからインポートする関数やメソッドのシグネチャを記述することで,Rust内で利用できるようにします.Javascript側で定義したエラーを簡単のためにerror.rsのエラーと同じ名前としているので,lib.rsからerror.rsの同名のエラーを利用する場合はcrate::error::LogSmallErrorのようにします.
マクロの引数としてmoduleを指定することで,wasm-packが出力するJavascriptファイルにimport文を含めることができます.ここではmath_lib/pkgディレクトリまでのパスを@math_libというエイリアスとしています.各エラーはextendsで継承するベースクラスを明示していますが,今回は各エラー同士を変換することはないため省いても大丈夫です.

lib.rs
#[wasm_bindgen(module = "@math_lib/wasm_error")]
extern "C" {
    #[wasm_bindgen]
    pub type MathError;

    #[wasm_bindgen(extends = MathError)]
    pub type LogSmallError;

    #[wasm_bindgen(constructor)]
    pub fn new(msg: String) -> LogSmallError;

    #[wasm_bindgen(extends = MathError)]
    pub type ExpLargeError;

    #[wasm_bindgen(constructor)]
    pub fn new(msg: String) -> ExpLargeError;
}

Rust側でエラーを返しJavascriptでキャッチする

wasm_bindgenではエラーとしてwasm_bindgen::JsValueを返す必要があるので,Fromトレイトを実装します.Fromトレイトを実装することで,定義した各エラーからResult<T, JsValue>?で返すことができます.

lib.rs
impl From<crate::error::LogSmallError> for JsValue {
    fn from(err: crate::error::LogSmallError) -> JsValue {
        LogSmallError::new(err.to_string()).into()
    }
}

impl From<crate::error::ExpLargeError> for JsValue {
    fn from(err: crate::error::ExpLargeError) -> JsValue {
        ExpLargeError::new(err.to_string()).into()
    }
}

通常のように関数を定義します.

lib.rs
fn log(x: f64) -> Result<f64, crate::error::LogSmallError> {
    if x < 0.01 {
        return Err(crate::error::LogSmallError(x));
    } else {
        return Ok(x.ln());
    }
}

fn exp(x: f64) -> Result<f64, crate::error::ExpLargeError> {
    if x > 10.0 {
        return Err(crate::error::ExpLargeError(x));
    } else {
        return Ok(x.exp());
    }
}

#[wasm_bindgen]
pub fn wasm_log(x: f64) -> Result<f64, JsValue> {
    Ok(log(x)?)
}

#[wasm_bindgen]
pub fn wasm_exp(x: f64) -> Result<f64, JsValue> {
    Ok(exp(x)?)
}

以上の関数をJavascript側から呼んで例外をキャッチすると以下のようになります,

main.ts
import init, {wasm_log, wasm_exp} from "@math_lib/math_lib";
import {MathError, LogSmallError, ExpLargeError} from "@math_lib/wasm_error"
import wasm_path from "@math_lib/math_lib_bg.wasm?url";

await init(wasm_path);

try{
  wasm_log(0.0);
} catch(e) {
  console.log("---- wasm_log ----");
  console.log(e);
  console.log(`e is instance of MathError: ${e instanceof MathError}`);
  console.log(`e is instance of LogSmallError: ${e instanceof LogSmallError}`);
  console.log(`e is instance of ExpLargeError: ${e instanceof ExpLargeError}`);
}

try{
  wasm_exp(1000);
} catch(e) {
  console.log("---- wasm_exp ----");
  console.log(e);
  console.log(`e is instance of ExpLargeError: ${e instanceof ExpLargeError}`);
}
---- wasm_log ----
main.ts:11 LogSmallError: too small x for log(x) x:0. (expected x > 0.01)
    at imports.wbg.__wbg_new_4b9dc3046e1dba61 (math_lib.js?t=1639234100779:132)
    at math_lib_bg.wasm:0xa344
    at math_lib_bg.wasm:0x9d4b
    at math_lib_bg.wasm:0xae8a
    at wasm_log (math_lib.js?t=1639234100779:54)
    at main.ts:8
main.ts:12 e is instance of MathError: true
main.ts:13 e is instance of LogSmallError: true
main.ts:14 e is instance of ExpLargeError: false
main.ts:20 ---- wasm_exp ----
main.ts:21 ExpLargeError: too large x for exp(x) x:1000. (expected x < 10)
    at imports.wbg.__wbg_new_fbc4d6542b13d2e9 (math_lib.js?t=1639234100779:140)
    at math_lib_bg.wasm:0xa3ad
    at math_lib_bg.wasm:0x9dfe
    at math_lib_bg.wasm:0xad3a
    at wasm_exp (math_lib.js?t=1639234100779:63)
    at main.ts:18
main.ts:22 e is instance of ExpLargeError: true

スーパークラスも含めて,正しくinstance ofで判別できています,

4
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
3