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

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

簡単なものですがプログラムは[こちら](https://github.com/deepgreenAN/wasm_bindgen_error_handling)にあります．

## エラーの定義
エラーは以下で定義しています．

```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部分で定義しているので，ここでは必要最低限の定義だけしています．

```typescript: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`で継承するベースクラスを明示していますが，今回は各エラー同士を変換することはないため省いても大丈夫です．

```rust: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>`に`?`で返すことができます．

```rust: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()
    }
}
```

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

```rust: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側から呼んで例外をキャッチすると以下のようになります，

```typescript: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`で判別できています，