(※この記事は 別媒体に投稿した記事 のバックアップです。 canonical も設定しています)
2021-08-01
最近学習している Rust はいろいろな環境で動せるので、いくつかの環境で実行できるか試してみた結果をまとめたメモです。
ベースとして使った Rust のソースコードは、以下のようにランダムな文字列を返すだけの関数です。
(ビルドのために変更が必要になるものもあるので、あくまでこれをベースにしてやった、という感じです)
use rand::{thread_rng, Rng};
use rand::distributions::Alphanumeric;
fn random() -> String {
thread_rng()
.sample_iter(&Alphanumeric)
.take(30)
.map(char::from)
.collect()
}
fn main() {
println!("Random: {}", random());
}
なお、こちらのサンプルコードを参考にしています。
以下の環境で実行しています。
$ sw_vers
ProductName: macOS
ProductVersion: 11.5.1
BuildVersion: 20G80
$ cargo --version
cargo 1.54.0 (5ae8d74b3 2021-06-22)
$ rustup --version
rustup 1.24.3 (ce5817a94 2021-05-31)
$ rustc --version
rustc 1.54.0 (a178d0322 2021-07-26)
これはよく見るやつです。
普通に cargo build
すればOKです。
$ cargo build --release
以下のコマンドで実行可能。
$ ./target/release/gen_rand
Random: QqdElRD0gD8pTH6hLuCqPeOchXEVxB
WebAssembly (wasm) はブラウザで実行できる、ネイティブに近いパフォーマンスで動作する方法です。
https://developer.mozilla.org/ja/docs/WebAssembly
https://webassembly.org/docs/web/
Unsurprisingly, one of WebAssembly’s primary purposes is to run on the Web, for example embedded in Web browsers (though this is not its only purpose).
MDNのドキュメントにいい感じのチュートリアルがあったので、こちらをベースに試してみました。
https://developer.mozilla.org/ja/docs/WebAssembly/Rust_to_wasm
npm に公開するためのパッケージを作るための作業を手軽にしてくれるようなイメージのツールです。
https://crates.io/crates/wasm-pack
$ cargo install wasm-pack
wasm-bindgen
という wasm と JavaScript のやり取りがしやすくなる Crate を追加しています。
また rand
で使われている getrandom
という Crate を wasm に変換する際は features = ["js"]
の指定が必要になるとドキュメントに書かれていたので、追加しています。
+ [lib]
+ crate-type = ["cdylib"]
[dependencies]
rand = "0.8.4"
+ getrandom = { version = "0.2", features = ["js"] }
+ wasm-bindgen = "0.2"
main.rs
を lib.rs
に変更して、ソースコードも以下のように変更しました。
+ extern crate wasm_bindgen;
+
+ use wasm_bindgen::prelude::*;
use rand::{thread_rng, Rng};
use rand::distributions::Alphanumeric;
// (略)
.collect()
}
- fn main() {
- println!("Random: {}", random());
+ #[wasm_bindgen]
+ pub fn gen_rand() -> String {
+ random()
}
cargo
ではなく wasm-pack
を使ってビルドします。
$ wasm-pack build --scope "(scope_name)"
ビルド結果は target
ではなく pkg
ディレクトリに出力されます。
wasm ファイルやエントリーポイントとなるJSファイル、TypeScriptの型定義ファイル ( .d.ts
) などが出力されています。
また package.json
など、npm への公開に必要な情報なども含まれています。
以下のコマンドで npm 用のパッケージを作成します。
(MDN のチュートリアルでは npm へ公開していますが、そこまでやる必要もないかな、と思ったので今回はローカルに置いたファイルを使うようにしてみました)
$ cd pkg
$ npm pack
webpack
でJSをビルドする環境を作りました。
{
"scripts": {
"start": "webpack serve"
},
"dependencies": {
"@mryhryki/gen_rand": "file:../pkg/mryhryki-gen_rand-0.1.0.tgz",
"webpack": "^5.47.1",
"webpack-cli": "^4.7.2",
"webpack-dev-server": "^3.11.2"
}
}
先に生成していた npm 用のパッケージは、以下のコマンドで package.json
に追加しました。
$ npm i ../pkg/mryhryki-gen_rand-0.1.0.tgz
webpack5
だと experiments.asyncWebAssembly = true
をセットしないと wasm のビルドができないようです。
(無しで実行すると丁寧なエラーメッセージが出てくれて優しい)
const path = require('path')
module.exports = {
mode: 'development',
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'public'),
filename: 'index.js',
},
experiments: {
asyncWebAssembly: true
},
devServer: {
contentBase: 'public/',
open: true,
},
}
以下のようにシンプルな HTML と JavaScript ファイルを用意しました。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Web Assembly Test</title>
</head>
<body>
<h1>Web Assembly Test</h1>
<div>
<button id="$generate">Generate Random Text</button>
</div>
<div style="margin-top: 16px;">
<textarea id="$result" style="width: 480px; max-width: 96vw; min-height: 320px;"></textarea>
</div>
<script src="./index.js"></script>
</body>
</html>
import { gen_rand as genRand } from '@mryhryki/gen_rand'
const generateButton = document.getElementById('$generate')
const resultElement = document.getElementById('$result')
generateButton.addEventListener('click', () => {
const random = genRand();
resultElement.value = `${resultElement.value}${random}\n`;
})
(wasm って同期的に実行できるんですね)
webpack-dev-server
を起動して、ブラウザで実行すると以下のように動作しました。
wasm-pack
や wasm-bindgen
などのツールがちゃんと動いてくれるおかげで、思っていたよりずっと簡単にできました。
あとMDNのドキュメントも丁寧で非常にわかりやすかったです。
Wasmer は WebAssembly をブラウザ以外で動作できる実行環境です。
https://wasmer.io/
WebAssembly は、ブラウザ以外で実行されることも想定されているようです。
https://webassembly.org/docs/non-web/
こちらの記事も参考になります。
https://zenn.dev/koduki/articles/f1b342079788be
こちらのドキュメントに従ってインストールします。
https://docs.wasmer.io/ecosystem/wasmer/getting-started
$ curl https://get.wasmer.io -sSfL | sh
## パスを通す
$ export PATH="${HOME}/.wasmer/bin:${PATH}"
バージョンは 2.0.0
です。
$ wasmer --version
wasmer 2.0.0
最初に以下のコマンドで wasm 向けにビルドできるように設定します。
$ rustup target add wasm32-wasi
以下のコマンドでビルドできます。
$ cargo build --release --target wasm32-wasi
出力された wasm 用のバイナリを wasmer
コマンドに渡せば実行できます。
$ wasmer run target/wasm32-wasi/release/gen_rand.wasm
Random: Bq1xx81UHB8ljCzRxql7XC5FhMzTIf
私が個人的によく使っているので、AWS Lambda での実行も試してみました。
lambda_runtime のリポジトリの README に従ってセットアップしました。
細かいところは省略して、コマンドだけ載せておきます。
$ rustup target add x86_64-unknown-linux-musl
$ brew install filosottile/musl-cross/musl-cross
$ mkdir .cargo
$ echo $'[target.x86_64-unknown-linux-musl]\nlinker = "x86_64-linux-musl-gcc"' > .cargo/config
実行に必要な Crate を追加しました。
[dependencies]
+ lambda_runtime = "0.3.0"
rand = "0.8.4"
+ serde = "1.0.126"
+ tokio = "1.9.0"
AWS Lambda 向けに以下のようにソースコードを変更しました。
+ use lambda_runtime::{handler_fn, Context, Error};
+ use serde::{Deserialize, Serialize};
use rand::{thread_rng, Rng};
use rand::distributions::Alphanumeric;
+ #[derive(Deserialize)]
+ struct Request {}
+
+ #[derive(Serialize)]
+ struct Response {
+ random: String,
+ }
+
fn random() -> String {
thread_rng()
.sample_iter(&Alphanumeric)
//...
.collect()
}
- fn main() {
- println!("Random: {}", random());
+ #[tokio::main]
+ async fn main() -> Result<(), Error> {
+ let func = handler_fn(handler);
+ lambda_runtime::run(func).await?;
+ Ok(())
+ }
+
+ pub(crate) async fn handler(_: Request, _: Context) -> Result<Response, Error> {
+ let resp = Response { random: random() };
+ Ok(resp)
}
オプションを変えて cargo build
を実行して、Linux 向けのバイナリを生成します。
$ cargo build --release --target x86_64-unknown-linux-musl
以下のコマンドで生成したバイナリを AWS Lambda 向けの Zip ファイルにします。
cp "target/x86_64-unknown-linux-musl/release/gen_rand" "./bootstrap"
zip "lambda.zip" "bootstrap"
※ bootstreap
以外の名前にしたり、Zip ファイル作成時のパスを上記以外にすると、Lambda 実行時にエラーになる場合があります。
AWS CLI を使って作成しました。
$ aws lambda create-function --function-name gen_rand \
--handler "gen_rand.handler" \
--zip-file "fileb://./lambda.zip" \
--runtime provided \
--role "arn:aws:iam::000000000000:role/IAM_ROLE_NAME" \
--environment "Variables={RUST_BACKTRACE=1}" \
--tracing-config "Mode=Active"
更新する場合はこちら。
$ aws lambda update-function-code --function-name gen_rand \
--zip-file "fileb://./lambda.zip"
こちらも AWS CLI 経由で実行しました。
$ aws lambda invoke --function-name gen_rand --payload '{}' "output.json"
$ cat output.json
{"random":"iomW3yjQwtLZbbfdvvbr3eHjhCKBMq"}
書いてある文量の3倍ぐらいは試した気がします。
それでも、思っていたよりは簡単にできた印象があります。
ツールやドキュメントが充実している気がしますね。
さすが最も愛されている言語です。
ブラウザ上での WebAssembly 実行は、当初 Vite を使ってみたんですが、こちらはうまく行かなかったです。
すぐ MDN に書いてあった Webpack に切り替えてうまくいったので深追いしていませんが、まだこの辺りは対応していないのかな?
(プラグイン入れればすぐ動くとかあるのかもしれない。未検証)
今回はとりあえず動くところまで試した感じなので、次はもうちょい中身を見ていきたい。rustup target add
とか cargo build --target XXX
とか中で何が起きてるかよくわからないので。