Rust のコードをいろいろな環境で動かしてみたメモ
はじめに
最近学習している Rust はいろいろな環境で動せるので、いくつかの環境で実行できるか試してみた結果をまとめたメモです。
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)
1. CLI
これはよく見るやつです。
普通に cargo build
すればOKです。
$ cargo build --release
以下のコマンドで実行可能。
$ ./target/release/gen_rand
Random: QqdElRD0gD8pTH6hLuCqPeOchXEVxB
2. WebAssembly
WebAssembly (wasm) はブラウザで実行できる、ネイティブに近いパフォーマンスで動作する方法です。
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のドキュメントにいい感じのチュートリアルがあったので、こちらをベースに試してみました。
wasm-pack のインストール
npm に公開するためのパッケージを作るための作業を手軽にしてくれるようなイメージのツールです。
$ cargo install wasm-pack
Cargo.toml の変更
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 パッケージの作成
以下のコマンドで npm 用のパッケージを作成します。
(MDN のチュートリアルでは npm へ公開していますが、そこまでやる必要もないかな、と思ったので今回はローカルに置いたファイルを使うようにしてみました)
$ cd pkg
$ npm pack
JSビルドの環境構築
webpack
でJSをビルドする環境を作りました。
package.json
{
"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
webpack.config.js
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
以下のようにシンプルな 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のドキュメントも丁寧で非常にわかりやすかったです。
3. Wasmer
Wasmer は WebAssembly をブラウザ以外で動作できる実行環境です。
WebAssembly は、ブラウザ以外で実行されることも想定されているようです。
こちらの記事も参考になります。
インストール
こちらのドキュメントに従ってインストールします。
$ curl https://get.wasmer.io -sSfL | sh
# パスを通す
$ export PATH="${HOME}/.wasmer/bin:${PATH}"
バージョンは 2.0.0
です。
$ wasmer --version
wasmer 2.0.0
wasm 向けにビルド
最初に以下のコマンドで 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
4. AWS Lambda
私が個人的によく使っているので、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
Cargo.toml の変更
実行に必要な Crate を追加しました。
[dependencies]
+ lambda_runtime = "0.3.0"
rand = "0.8.4"
+ serde = "1.0.126"
+ tokio = "1.9.0"
main.rs の変更
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)
}
Linux 向けにビルドする
オプションを変えて cargo build
を実行して、Linux 向けのバイナリを生成します。
$ cargo build --release --target x86_64-unknown-linux-musl
生成したバイナリをZipファイルにまとめる
以下のコマンドで生成したバイナリを AWS Lambda 向けの Zip ファイルにします。
cp "target/x86_64-unknown-linux-musl/release/gen_rand" "./bootstrap"
zip "lambda.zip" "bootstrap"
※ bootstreap
以外の名前にしたり、Zip ファイル作成時のパスを上記以外にすると、Lambda 実行時にエラーになる場合があります。
AWS 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 Lambda の実行
こちらも 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
とか中で何が起きてるかよくわからないので。
Discussion