Open15

Rust & WebAsssemblyメモ

botamotchbotamotch

WebAssemblyの概要

https://developer.mozilla.org/ja/docs/WebAssembly/Concepts

WebAssemblyとは何か

ウェブ上で動作するクライアントアプリで従来は実現できなかったネイティブ水準の速度で複数の言語で記述されたコードをウェブ上で動作させる方法を提供します。

C/C++、Rust等の低水準の言語にとって効果的なコンパイル対象となるように設計されています

WebAssemblyの目標

  • 高速、高効率、ポータブルであること
  • 可読性を持ちデバッグ可能であること
  • サンドボックスで実行し、安全であること。サンドボックス
  • ウェブを破壊しないこと。後方互換性を維持するように設計する

WebAssemblyはどのようにウェブプラットフォームに適合するのか

  • ウェブアプリのコードを実行する仮想マシン(VM)
  • ウェブブラウザ/デバイスの機能をコントロールするために呼ぶことのできるAPIのセット(DOM、WebGL等)

以前、仮想マシンはJavaScriptだけを読み込むことができました

仮想マシン上でJavaScript(.js)を実行するのと同じようにWebAssembly(.wasm)を実行することができるってことか。単にC/C++, RustのコードをJavaScriptに変換するものだと思ってたけど違うのね

JavaScriptは高水準の言語であり、ウェブアプリケーションを作る上で十分な柔軟性と表現力を持っています

WebAssemblyはネイティブに近いパフォーマンスで動作して、(略)コンパクトなバイナリー形式を持つ低水準なアセンブリーに似た言語です

WebAssemblyの主要概念

  • モジュール:ブラウザーによって実行可能な機械語にコンパイルされたバイナリ
  • メモリー:メモリーアクセス命令によって読み書きが行われるバイト列を保持するArrayBuffer
  • テーブル:メモリー内に保持できなかった関数等に対する参照を保持している配列
  • インスタンス:メモリー、テーブル、インポートされた値を含むすべての状態と対となるモジュール(???)

WebAssemblyをどのようにアプリで用いるか

botamotchbotamotch

RustからWebAssemblyにコンパイル

https://developer.mozilla.org/ja/docs/WebAssembly/Rust_to_wasm

$ cargo new --lib hello-wasm
$ cd hello-wasm
src/lib.rs
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
extern {
    pub fn alert(s: &str);
}
#[wasm_bindgen]
pub fn greet(name: &str) {
    alert(&format!("Hello, {}!", name));
}
Carog.toml
[lib]
crate-type = ["cdylib"]
[dependencies]
wasm-bindgen = "0.2"
$ wasm-pack build --target web
index.html
<!doctype html>
<html lang="en-US">
  <head>
    <meta charset="utf-8" />
    <title>hello-wasm example</title>
  </head>
  <body>
    <script type="module">
      import init, { greet } from "./pkg/hello_wasm05.js";
      init().then(() => {
        greet("WebAssembly");
      });
    </script>
  </body>
</html>
botamotchbotamotch

例1)ウェブで実行する

こんな感じ。

ブラウザからhello_wasm05.jshello_wasm_bg.wasmを取得してる。

botamotchbotamotch

例2)Deno/Freshで実行する

実行できない。

island-wasm_default.jsを取得できているけど、pkg_bg.wasmの取得で失敗してるのでコケてる。

っていうか.jsファイルの名前もよくわかんないことになってる。Freshフレームワークの詳しく調べないとだめかな

botamotchbotamotch

https://deno.land/x/wasmbuild@0.15.0
https://toranoana-lab.hatenablog.com/entry/2023/01/31/100000
https://rodneylab.com/deno-fresh-wasm/

wasmbuild使って static ディレクトリに .wasmのハードリンク貼って instantiate() したら動いた

いやハードリンク貼れとか公式ドキュメントのどこにも書いてないんだけどなんなの。staticに置く以外に取得する方法あるの。issueとか見てみるか

deno.json
{
  "tasks": {
    "wasmbuild": "deno run -A https://deno.land/x/wasmbuild@0.15.0/main.ts"
  }

  "imports": {
    "@/": "./",
  },
}
$ deno task wasmbuild new
$ deno task wasmbuild
$ ln lib/rs_lib_bg.wasm static/
islands/Add.tsx
import { add, instantiate } from "../lib/rs_lib.generated.js";

async function Add() {
  await instantiate({
    url: new URL("rs_lib_bg.wasm", location.origin),
  });
  console.log(add(1, 2));
}

export function AddButton() {
  return (
    <button
      class="bg-gray-100 border border-gray-700 p-2 rounded"
      onClick={() => {
        Add();
      }}
    >
      wasm add
    </button>
  );
}
botamotchbotamotch

な〜んかwasmのパス指定がうまく働いてないっぽいんだよな。import.meta.urlってなんだ?

islands/Add.tsx
// OK http://localhost:8000/rs_lib_bg.wasm
await instantiate({
  url: new URL("rs_lib_bg.wasm", location.origin),
});

// NG http://localhost:8000/_frsh/js/fc6548ada89eababcddbd27f5c98a9fa5aace5d4/rs_lib_bg.wasm
await instantiate({
  url: new URL("rs_lib_bg.wasm", import.meta.url),
});
botamotchbotamotch

Option<T>

取得できない可能性がある値を表現するための列挙型。基本的にはunwrapで取り出す。中身はmatchによる分岐

std.option
pub enum Option<T> {
    None,
    Some(T),
}
option.rs
impl<T> Option<T> {
    pub const fn unwrap(self) -> T {
        match self {
            Some(val) => val,
            None => panic("called `Option::unwrap()` on a `None` value"),
        }
    }
}

Result<T,E>

実行できない処理の結果を表現するための列挙型。Optionと同様、unwrapなどで値を取り出す

std.result
pub enum Result<T, E> {
    Ok(T),
    Err(E),
}
result.rs
impl<T, E> Result<T, E> {
    pub fn unwrap(self) -> T
    where
        E: fmt::Debug,
    {
        match self {
            Ok(t) => t,
            Err(e) => unwrap_failed("called `Result::unwrap()` on an `Err` value", &e),
        }
    }
}
botamotchbotamotch

やっぱ公式のドキュメントは丁寧に書いてある。

botamotchbotamotch

pub fn ok_or_else<E, F>(self, err: F) -> Result<T, E>

Option<T>Result<T,E>に変換する。Some(v)Ok(v)に、NoneErr(err())にマッピングされる。

botamotchbotamotch

参照

  • & reference, 参照
  • &mut mutable reference, 可変参照
  • * dereference 参照外し
  • 参照は同時に何個でも同時に存在できる
  • 不変参照を作成した時、他の参照/不変参照は無効になる
  • 参照先のオブジェクトを操作するときは参照外しを使用する
fn main() {
    let mut a = 10;           // mutable object
    let a_ref1 = &a;          // reference
    let a_mut_ref1 = &mut a;  // mutable reference / a_ref1 is disable
    let a_mut_ref2 = &mut a;  // mutable reference / a_mut_ref1 is disable
    let a_ref2 = &a;          // reference / a_mut_ref2 is disable
    println!("{}", a_ref2);   // borrow check!! - OK
}
botamotchbotamotch

トレイト

共通の振る舞いを定義するもの。他の言語における『インターフェース』に近い

トレイトではメソッドシグニチャ(型)を定義する。この時、デフォルトの実装を指定することもできる。

Summaryトレイトを定義する
pub trait Summary {
    fn summarize(&self) -> String;
}

impl A for Bで、実際の型/構造体に対してトレイトの中身を定義する

NewsArticle構造体に対してSummaryトレイトを実装する
pub struct NewsArticle {
    pub headline: String,
    pub location: String,
    pub author: String,
    pub content: String,
}

impl Summary for NewsArticle {
    fn summarize(&self) -> String {
        format!("{}, by {} ({})", self.headline, self.author, self.location)
    }
}
Tweet構造体に対してSummaryトレイトを実装する
pub struct Tweet {
    pub username: String,
    pub content: String,
    pub reply: bool,
    pub retweet: bool,
}

impl Summary for Tweet {
    fn summarize(&self) -> String {
        format!("{}: {}", self.username, self.content)
    }
}

トレイトを関数の引数に取ることで、いろいろな種類の型を受け付ける関数を定義することができる。impl Trait構文で解決できない複雑なケースでは『トレイト境界構文』を使うことでで表現できる。

pub fn notify(item: &impl Summary) {
    println!("Breaking news! {}", item.summarize());
}
botamotchbotamotch
  • トレイト・ジェネリック
  • クレート
  • Option, Result
  • 参照
  • ライフタイム
  • クロージャ