Rust & WebAsssemblyメモ
WebAssemblyについて真面目に勉強する
WebAssemblyの概要
WebAssemblyとは何か
ウェブ上で動作するクライアントアプリで従来は実現できなかったネイティブ水準の速度で複数の言語で記述されたコードをウェブ上で動作させる方法を提供します。
C/C++、Rust等の低水準の言語にとって効果的なコンパイル対象となるように設計されています
WebAssemblyの目標
- 高速、高効率、ポータブルであること
- 可読性を持ちデバッグ可能であること
- サンドボックスで実行し、安全であること。サンドボックス
- ウェブを破壊しないこと。後方互換性を維持するように設計する
WebAssemblyはどのようにウェブプラットフォームに適合するのか
- ウェブアプリのコードを実行する仮想マシン(VM)
- ウェブブラウザ/デバイスの機能をコントロールするために呼ぶことのできるAPIのセット(DOM、WebGL等)
以前、仮想マシンはJavaScriptだけを読み込むことができました
仮想マシン上でJavaScript(.js)を実行するのと同じようにWebAssembly(.wasm)を実行することができるってことか。単にC/C++, RustのコードをJavaScriptに変換するものだと思ってたけど違うのね
JavaScriptは高水準の言語であり、ウェブアプリケーションを作る上で十分な柔軟性と表現力を持っています
WebAssemblyはネイティブに近いパフォーマンスで動作して、(略)コンパクトなバイナリー形式を持つ低水準なアセンブリーに似た言語です
WebAssemblyの主要概念
- モジュール:ブラウザーによって実行可能な機械語にコンパイルされたバイナリ
- メモリー:メモリーアクセス命令によって読み書きが行われるバイト列を保持するArrayBuffer
- テーブル:メモリー内に保持できなかった関数等に対する参照を保持している配列
- インスタンス:メモリー、テーブル、インポートされた値を含むすべての状態と対となるモジュール(???)
WebAssemblyをどのようにアプリで用いるか
RustからWebAssemblyにコンパイル
$ cargo new --lib hello-wasm
$ cd hello-wasm
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
extern {
pub fn alert(s: &str);
}
#[wasm_bindgen]
pub fn greet(name: &str) {
alert(&format!("Hello, {}!", name));
}
[lib]
crate-type = ["cdylib"]
[dependencies]
wasm-bindgen = "0.2"
$ wasm-pack build --target web
<!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>
例1)ウェブで実行する
こんな感じ。
ブラウザからhello_wasm05.js
とhello_wasm_bg.wasm
を取得してる。
例2)Deno/Freshで実行する
実行できない。
island-wasm_default.js
を取得できているけど、pkg_bg.wasm
の取得で失敗してるのでコケてる。
っていうか.jsファイルの名前もよくわかんないことになってる。Freshフレームワークの詳しく調べないとだめかな
wasmbuild使って static ディレクトリに .wasmのハードリンク貼って instantiate() したら動いた
いやハードリンク貼れとか公式ドキュメントのどこにも書いてないんだけどなんなの。staticに置く以外に取得する方法あるの。issueとか見てみるか
{
"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/
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>
);
}
な〜んかwasmのパス指定がうまく働いてないっぽいんだよな。import.meta.url
ってなんだ?
// 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),
});
WebAssemblyのエッジコンピューティングというのが既にあるのね
Option<T>
取得できない可能性がある値を表現するための列挙型。基本的にはunwrap
で取り出す。中身はmatch
による分岐
pub enum Option<T> {
None,
Some(T),
}
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などで値を取り出す
pub enum Result<T, E> {
Ok(T),
Err(E),
}
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),
}
}
}
やっぱ公式のドキュメントは丁寧に書いてある。
pub fn ok_or_else<E, F>(self, err: F) -> Result<T, E>
Option<T>
をResult<T,E>
に変換する。Some(v)
はOk(v)
に、None
はErr(err())
にマッピングされる。
rust analyzerのドキュメント
参照
-
&
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
}
トレイト
共通の振る舞いを定義するもの。他の言語における『インターフェース』に近い
トレイトではメソッドシグニチャ(型)を定義する。この時、デフォルトの実装を指定することもできる。
pub trait Summary {
fn summarize(&self) -> String;
}
impl A for B
で、実際の型/構造体に対してトレイトの中身を定義する
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)
}
}
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());
}
- トレイト・ジェネリック
- クレート
- Option, Result
- 参照
- ライフタイム
- クロージャ