🔮

Rust の WASM フレームワーク "Yew" に入門しおみくじアプリを作成する

2024/04/04に公開

この記事では、Rustでウェブアプリケーションを構築するためのフレームワーク、Yewのセットアップと使い始め方について調査した内容を記載します。

Yew とは?

Yewは、Rustで書かれたモダンなWebアプリケーションフレームワークです。フロントエンドとバックエンドの両方をRustで開発できる、いわゆるフルスタックのフレームワークです。

フロントエンド機能

  • React、Vue、Angularなどと同様のコンポーネントベースのアーキテクチャを採用
  • Rustの特性を活かした以下のメリットがあります
    • 型安全性: コンパイル時にエラーを検出でき、実行時エラーを防げます
    • 高パフォーマンス: Rustの高速性を生かせます
    • メモリ安全性: データ競合などのメモリ安全性問題が起きにくくなります

フルスタック開発のメリット

  • 一つの言語(Rust)で開発できるため、学習コストを削減できます
  • コード全体で一貫した構文やツールを使えるので、開発効率が向上します
  • フロントエンドとバックエンドで同じデータ構造を使え、データの受け渡しが簡単になります

つまり、Yewを使えばRustの安全性、並行性、高速性をフルスタックで活かしたモダンなWebアプリケーション開発が可能になります。

Rustがフロントエンドで動作する理由

Rustがフロントエンドで動作できるのは、WebAssembly(Wasm) という技術のおかげです。

WebAssemblyとは、ウェブブラウザで高速に実行できるバイナリフォーマットです。JavaScriptと並行して動作し、ネイティブアプリケーションに近いパフォーマンスを発揮できます。

Rustは、その設計思想とパフォーマンスの高さからWebAssemblyの開発言語に適しているとされます。Rustで書かれたコードは、WebAssemblyにコンパイルすることで、ウェブブラウザ上で直接実行できます。

つまり、WebAssemblyを介してRustをフロントエンドで活用できるため、高速で安全なウェブアプリケーション開発が可能になります。Rustの強みを最大限に生かしつつ、JavaScriptとも親和性が高いのが特徴です。

「おみくじ」アプリを作成する

Yewフレームワークを使って「おみくじ」アプリを作成します。
ボタンをクリックすると、ランダムにおみくじの結果が選ばれ、画面に表示されるアプリです。

1. 前提条件と環境のセットアップ

まずは、Rustの開発環境をセットアップします。

Windowsの場合は、Microsoft公式ドキュメントを参考にしてください。

次に、WebAssemblyバイナリをビルドするための設定を行います。

rustup target add wasm32-unknown-unknown
  • rustupはRustツールチェーンのインストーラーで、ツールチェーンの管理と更新に使用します
  • target addで新しいターゲットプラットフォームをコンパイルに追加します
  • wasm32-unknown-unknownはWebAssembly(Wasm)出力用のターゲットで、Rustをブラウザで実行できるようになります

最後に、WebAssemblyアプリケーションのビルドとデプロイを可能にするTrunkパッケージをインストールします。

cargo install --locked trunk
  • cargo installでRustのパッケージマネージャ(Cargo)を使ってパッケージをインストールします
  • --lockedフラグで、Cargo.lockファイルに記録された依存関係のバージョンを使用するよう指示し、ビルドの再現性を高めます
  • trunkはWebAssemblyアプリのビルド・デプロイツールです

以上で準備が整いましたので、おみくじアプリの作成に取り掛ります。

2. 新しい Yew プロジェクトの作成

次に、新しいYewプロジェクトを作成します。以下のコマンドで新規プロジェクトを作成してください。

# --libフラグを指定してライブラリ(lib.rs)を作成します
cargo new --lib yew_fortune_app

cd yew_fortune_app
  • cargo newでCargoプロジェクトを新規作成します
  • --libフラグを渡すことで、バイナリではなくライブラリを作成するよう指示しています
  • yew_fortune_appは新しいプロジェクトの名前です

Yewアプリはライブラリとして実装されるため、--libフラグを指定してプロジェクトを作成しています。

作成されたプロジェクトにはsrc/lib.rsファイルがあり、ここにアプリケーションのコードを記述していきます。

3. 依存関係を追加する

新しいプロジェクトが作成されたら、Cargo.tomlファイルに必要な依存関係を追加します。

[package]
name = "yew_fortune_app"
version = "0.1.0"
edition = "2021"

[lib]
crate-type = ["cdylib", "rlib"]

[dependencies]
yew = "0.17"
wasm-bindgen = "0.2"
rand = "0.8"

# WebベースのWASMアプリでは、getrandomの機能を直接有効化する必要があります
getrandom = { version = "0.2", features = ["js"] }
  • [lib]セクションでcrate-typecdylibrlibに設定しています。
    • cdylib: WebAssemblyコンパクトデシリアライズドバイナリを出力します。
    • rlib: Rustライブラリを出力します。
  • [dependencies]セクションで、アプリに必要な依存関係を記載しています。
    • yew: Yewフレームワーク本体です。
    • wasm-bindgen: Rust/WebAssembly間のデータバインディングを行います。
    • rand: 乱数生成ライブラリです。おみくじ結果をランダムに選ぶため使用します。
    • getrandom: 安全な乱数生成のための依存ライブラリで、WebAssemblyターゲット向けに"js"フィーチャを有効化しています。

4. シンプルな「おみくじ」アプリケーションを書く

次に、シンプルな「おみくじ」アプリケーションのコードを書きます。
src/lib.rsファイルを以下のように編集します。

// Rustの標準ライブラリからrand(乱数生成)をインポート
// Web上で動作するためのwasm_bindgenライブラリから、preludeモジュールをインポート
// Yewから、preludeモジュールをインポート
use rand::Rng;
use wasm_bindgen::prelude::*;
use yew::prelude::*;

// Modelという構造体を定義
// Reactでいうところのコンポーネントに相当する
// ComponentLinkはコンポーネント内のイベントハンドリングに使用
// fortuneはおみくじの結果を保持する文字列
struct Model {
   link: ComponentLink<Self>,
   fortune: String,
}

// コンポーネントのメッセージ(イベント)を定義
// Msg::DrawFortuneがおみくじを引くイベント
enum Msg {
   DrawFortune,
}

// Modelに対してComponentトレイトを実装
impl Component for Model {
   // メッセージの型を指定
   type Message = Msg;
   // プロパティの型を指定(本例ではプロパティを持たない)
   type Properties = ();

   // コンポーネントの初期化処理
   // React のクラスコンポーネントの constructor に相当
   fn create(_: Self::Properties, link: ComponentLink<Self>) -> Self {
       Self {
           link,
           fortune: String::from("おみくじを引いてみてください!"),
       }
   }

   // メッセージ(イベント)が発生した際の処理
   // React の componentDidUpdate に相当
   fn update(&mut self, msg: Self::Message) -> ShouldRender {
       match msg {
           // DrawFortuneイベントが発生した場合
           Msg::DrawFortune => {
               // 乱数生成器を初期化
               let mut rng = rand::thread_rng();
               // おみくじの結果の候補を定義
               let fortunes = vec!["大吉", "中吉", "小吉", "吉", "凶"];
               // 乱数を使って候補からおみくじの結果を選択し、fortuneに格納
               self.fortune = fortunes[rng.gen_range(0..fortunes.len())].to_string();
               // コンポーネントの再描画を指示
               true
           }
       }
   }

   // プロパティが変更された際の処理
   // React の componentWillReceiveProps に相当
   // 本例ではプロパティを持たないので、常にfalseを返す
   fn change(&mut self, _props: Self::Properties) -> ShouldRender {
       false
   }

   // コンポーネントの描画処理
   // React の render に相当
   fn view(&self) -> Html {
       // JSXライクなマクロを使ってHTMLを生成
       html! {
           <div>
               // ボタンをクリックした際のイベントハンドラを登録
               <button onclick=self.link.callback(|_| Msg::DrawFortune)>{ "おみくじを引く" }</button>
               // おみくじの結果を表示
               <p>{ &self.fortune }</p>
           </div>
       }
   }
}

// WebAssemblyのエントリポイントを定義
#[wasm_bindgen(start)]
pub fn run_app() {
   // ModelコンポーネントをDOMツリーにマウント
   App::<Model>::new().mount_to_body();
}

このコードはWebAssemblyを使用してブラウザで実行されます。#[wasm_bindgen(start)]アトリビュートが付けられたrun_app関数が、アプリケーションのエントリーポイントとして機能します。この関数が呼び出されると、Yewアプリケーションが起動し、Modelがルートコンポーネントとしてレンダリングされます。

次にstaticフォルダを作成し、その中にindex.htmlファイルを追加します。

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8" />
    <title>Yew Fortune App</title>
    <script type="module">
      import init from "./wasm.js";
      init();
    </script>
  </head>
  <body></body>
</html>
  • <script type="module">:type="module"属性は、このスクリプトがES Modulesであることを示します。
  • import init from "./wasm.js";:init関数をwasm.jsファイルからインポートします。wasm.jsはアプリケーションをビルドする際に生成されます。
  • init();:インポートしたinit関数を呼び出します。この関数でWebAssemblyアプリケーションを初期化します。
    これでシンプルな「おみくじ」アプリケーションのコードが書けました。次は実際にビルドして動作確認します。

5. アプリケーションの実行と表示

最後に、アプリケーションをビルドしてブラウザで実行・表示します。

まず、以下のコマンドでWebAssemblyパッケージをビルドし、./staticディレクトリにwasmという名前で出力します。

wasm-pack build --target web --out-name wasm --out-dir ./static
  • wasm-pack build: wasm-packツールを使ってWebAssemblyパッケージをビルドします
  • --target web: ビルドターゲットをWebに設定し、ブラウザで直接実行できるよう出力します
  • --out-name wasm: 出力ファイル名をwasm(wasm.js)に設定します
  • --out-dir ./static: ビルド成果物の出力ディレクトリを./staticに設定します

次に、Rustの最新の開発版ツールチェーンをインストールし、それを使ってminiserveをインストールします。

rustup toolchain install nightly-x86_64-pc-windows-msvc
cargo +nightly install miniserve
  • rustup toolchain install nightly-x86_64-pc-windows-msvc: Rustの最新開発版ツールチェーン(nightly)を64bit Windowsに合わせてインストールします
  • cargo +nightly install miniserve: miniserveという小さな高速HTTPファイルサーバをインストールします。+nightlyフラグで開発版ツールチェーンを使用するよう指示しています

最後に、./staticディレクトリをホストします。

miniserve ./static --index index.html
  • miniserve ./static: miniserveを使って./staticディレクトリをホストします
  • --index index.html: サーバのデフォルトインデックスページをindex.htmlに設定します

これで http://127.0.0.1:8080 にアクセスすると、作成した「おみくじ」アプリが表示されます。

おみくじアプリ

「おみくじを引く」ボタンを押すと、ランダムにおみくじの結果が表示されます。
以上で、RustとYewを使ったWebアプリケーションの作成が完了しました。

関数型コンポーネントを使ってみる

Yewの最新版では、Function Componentsと呼ばれる新しい形式のコンポーネントが導入されています。これはReactのFunction Componentsに似ており、よりシンプルで直感的なコードを書くことができます。

1. Yewのバージョンを上げる

Yewのバージョンを0.17からFunction Componentsがサポートされる0.19にバージョンアップします。Cargo.tomlを次のように修正します。

yew = "0.19"

2. 「おみくじ」アプリケーションを修正する

src/lib.rsファイルを以下のように編集します。

use rand::Rng;
use wasm_bindgen::prelude::*;
use yew::prelude::*;

// Reactの関数コンポーネントに相当します。Fortuneという名前の関数コンポーネントを定義します。
#[function_component(Fortune)]
fn fortune() -> Html {
    // ReactのuseStateに相当します。おみくじの結果を保持する状態を作成します。
    let fortune = use_state(|| String::from("おみくじを引いてみてください!"));

    // ReactのuseCallbackに相当します。おみくじを引くためのコールバック関数を定義します。
    let draw_fortune = Callback::from({
        let fortune = fortune.clone();
        move |_| {
            let mut rng = rand::thread_rng();
            let fortunes = vec!["大吉", "中吉", "小吉", "吉", "凶"];
            fortune.set(fortunes[rng.gen_range(0..fortunes.len())].to_string())
        }
    });

    // ReactのJSXに相当します。HTMLを返します。
    html! {
        <div>
            <button onclick={draw_fortune}>{ "おみくじを引く" }</button>
            <p>{ &*fortune }</p>
        </div>
    }
}

// ReactのReactDOM.renderに相当します。アプリケーションを起動するための関数を定義します。
#[wasm_bindgen(start)]
fn run_app() {
    // Yewアプリケーションを起動します。Fortuneコンポーネントがルートコンポーネントとして使用されます。
    yew::start_app::<Fortune>();
}

先ほどと同様にビルドとホスティングを行うことができます。

wasm-pack build --target web --out-name wasm --out-dir ./static
miniserve ./static --index index.html

このように、Function Componentsを使うと、コンポーネントのロジックをひとまとめにできるので、コードがシンプルになり可読性が向上します。
ReactのFunction Componentsと同様に、フックも利用できます。この例ではuse_stateフックを使って状態管理を行っています。
Function Componentsの導入により、Yewのコンポーネント記述がより直感的で分かりやすくなりました。

Discussion