👻

RustのWasmでDOM操作、Canvas操作、API操作のサンプルを作ってみた

2022/12/24に公開

alert処理

まずはjavascriptのalert関数を呼び出してみます。
受け取ったname引数をjavascript標準のalertを使って通知するだけの関数です。

#[wasm_bindgen]
pub fn js_alert(name: &str) {
    alert(&format!("Hello, {}!", name));
}

下記のようなコマンドでビルド後、

$ wasm-pack build --release --target web -d ./dist/js

javascriptからは下記のように呼び出せます。

import init, { js_alert } from "../sample/dist/js/sample.js";
init().then(() => {
    // js alertへのアクセス
    js_alert("WebAssembly")
});

DOM操作

次に指定したIDのDOMに対してHtmlを挿入してみます。

#[wasm_bindgen]
pub fn insert_dom_message(param: &str) -> Result<(), JsValue> {
    let window = web_sys::window().expect("no global `window` exists");
    let document = window.document().expect("should have a document on window");
    let element = document.get_element_by_id("set_message").unwrap();

    let msg = format!("{}{}{}", "<p>Hello from Rust! ", param, "</p>");
    element.insert_adjacent_html("afterbegin", &msg)?;

    Ok(())
}

documentからIDを指定してelementを取得して、そのelementにhtmlをinsertしています。
こちらも先程と同じようにjavascript側から簡単に呼び出せます。

LocalStrage操作

LocalStrageも以下のようなコードでセット可能です。

pub fn set_local_storage(key: &str, value: &str) -> Result<(), JsValue> {
    let window = web_sys::window().unwrap();
    if let Ok(Some(local_storage)) = window.local_storage() {
        local_storage.set_item(key, value).unwrap();
    }

    Ok(()) 
}

Canvas操作

Canvasも以下のようなコードで描画可能です。

#[wasm_bindgen]
pub fn draw_canvas() {
    let document = web_sys::window().unwrap().document().unwrap();
    let canvas = document.get_element_by_id("canvas").unwrap();
    let canvas: web_sys::HtmlCanvasElement = canvas
        .dyn_into::<web_sys::HtmlCanvasElement>()
        .map_err(|_| ())
        .unwrap();

    let context = canvas
        .get_context("2d")
        .unwrap()
        .unwrap()
        .dyn_into::<web_sys::CanvasRenderingContext2d>()
        .unwrap();

    context.begin_path();

    // Draw the outer circle.
    context
        .arc(75.0, 75.0, 50.0, 0.0, f64::consts::PI * 2.0)
        .unwrap();

    // Draw the mouth.
    context.move_to(110.0, 75.0);
    context.arc(75.0, 75.0, 35.0, 0.0, f64::consts::PI).unwrap();

    // Draw the left eye.
    context.move_to(65.0, 65.0);
    context
        .arc(60.0, 65.0, 5.0, 0.0, f64::consts::PI * 2.0)
        .unwrap();

    // Draw the right eye.
    context.move_to(95.0, 65.0);
    context
        .arc(90.0, 65.0, 5.0, 0.0, f64::consts::PI * 2.0)
        .unwrap();

    context.stroke();
}

ここではニコちゃんが描画されています。

rust wasmを学習するページではCanvasを使ったゲームの実装例が明示されています。
https://rustwasm.github.io/docs/book/game-of-life/debugging.html#using-a-debugger-to-pause-between-each-tick

http get操作

以下はgithubのapiをコールしてブランチ情報を取得して返却しています。

#[wasm_bindgen]
pub async fn http_get() -> Result<JsValue, JsValue> {
    let result = reqwest::Client::new()
        .get("https://api.github.com/repos/rustwasm/wasm-bindgen/branches/master")
        .header("Accept", "application/vnd.github.v3+json")
        .send()
        .await;

    let res;
    match result {
        Ok(n) => res = n,
        Err(e) => {
            println!("failed to parse: {}", e);
            return Err(JsValue::from_bool(false));
        },
    }

    let result = res.text().await;
    let text;
    match result {
        Ok(n) => text = n,
        Err(e) => {
            println!("failed to parse: {}", e);
            return Err(JsValue::from_bool(false));
        },
    }

    let branch_info: Branch = serde_json::from_str(&text).unwrap();

    Ok(JsValue::from_serde(&branch_info).unwrap())
}

この辺りにweb requestの例などが掲載されています。
https://rust-lang-nursery.github.io/rust-cookbook/web/clients.html

このhttp getの内部処理ですが、osリソースを使用したrustの通信処理が実行されているのではなくreqwest の内部処理では下記のように、
https://github.com/seanmonstar/reqwest/blob/cdbf84feb1d86b8288796631f2120de5363b3ba9/src/wasm/client.rs#L13

あくまでjavascriptのfetch処理をrustがcallしているという仕組みのようです。

尚、セキュリティなどの関係もありwasmはOSリソースにはアクセスはできず、WebAssemblyとして許可されたリソースにしかアクセスできません。

fetch APIについては下記が詳しく掲載してくれています。
https://rustwasm.github.io/wasm-bindgen/examples/fetch.html

javascriptからの呼び出しは非同期になるので下記のようになります。

import init, { js_alert } from "../sample/dist/js/sample.js";
init().then(() => {
    async function init_proc() {
        // HTTP GET リクエスト
        const resp_get = await http_get();
        console.log(resp_get)
    }
    init_proc()
});

まとめ

rust wasmの交流の為のリポジトリも用意されているようです。
https://github.com/rustwasm/team

rust wasmに関する情報はまだ多くないので、不明点や議論したことがあれば、上記プルリクに乗っているdiscord serverなどに参加してみてもいいかもしれません。

wasmはjavascriptが担っていた操作のかなりの処理の代替も可能になっています。
rubyなどもwasmが使えるようになってきており、これから先もっとwasmの活用が活発になるかもしれません。

Discussion