じゃあ、おうちで学べる

本能を呼び覚ますこのコードに、君は抗えるか

RustとYewでポモドーロタイマーを作ってみた

はじめに

イベントで定期的に登壇をしていて休日出勤したのに消化しきれなかった代休が余っていたので、3連休爆誕させて以前から興味のあったWebAssemblyの学習に時間を使ってみることにしました(いくつか本も読んだのでいつか紹介します。)。「とりあえず何か作ってみる」のが一番の近道だと考え、日々の私生活でも使えそうなポモドーロタイマーを実装することにしました。

WebAssemblyのエコシステムを調べていく中で、RustのYewというフレームワークが目に留まりました。ReactライクなAPIでWebAssemblyアプリケーションが作れるとのことで、ちょっと開発することにしました。

github.com

使用技術の選定理由

  • Rust:WebAssemblyのための優れたツールチェーンを持っており、学習コストは高めですが、型安全性と高いパフォーマンスが魅力でした
  • Yew:ReactライクなAPIで、コンポーネントベースの開発が可能。フロントエンド開発者にとって親しみやすい設計になっています
  • Trunk:ビルドツールとして採用。trunk serveだけで開発サーバーが立ち上がる手軽さが気に入りました

技術スタック

プロジェクトの概要

ポモドーロテクニックは25分の作業時間と5分の休憩を交互に行う時間管理手法です。今回実装したアプリケーションでは、以下の機能を提供します:

  1. 基本的なタイマー機能

    • 25分のカウントダウン
    • 開始/停止/リセット制御
    • 視覚的なフィードバック
  2. タスク管理機能

    • タスクの記録
    • 実際の作業時間の追跡
    • 完了したタスクの履歴管理
  3. データエクスポート機能

実装の詳細

プロジェクトのセットアップ

まず、必要なツールとターゲットをインストールします:

rustup target add wasm32-unknown-unknown
cargo install trunk

主要なデータ構造

タスクとタイマーの状態管理のための構造体:

#[derive(Clone, Debug)]
struct Task {
    description: String,
    completed_at: DateTime<Local>,
    duration: i32,
}

pub struct PomodoroTimer {
    time: i32,
    running: bool,
    interval: Option<Interval>,
    current_task: String,
    completed_tasks: Vec<Task>,
    initial_time: i32,
    markdown_visible: bool,
    markdown_content: String,
}

タイマー機能の実装

Yewのコンポーネントシステムを利用して、タイマーの制御を実装しました:

fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
    match msg {
        Msg::Start => {
            if !self.running {
                let link = ctx.link().clone();
                self.interval = Some(Interval::new(1000, move || {
                    link.send_message(Msg::Tick);
                }));
                self.running = true;
            }
            true
        }
        // 他のメッセージハンドラ...
    }
}

クリップボード機能の実装

WebAssemblyからブラウザのクリップボードAPIを利用する実装:

Msg::CopyToClipboard => {
    if let Some(window) = window() {
        let navigator = window.navigator();
        let clipboard = navigator.clipboard();
        let _ = clipboard.write_text(&self.markdown_content);
    }
    true
}

開発で直面した課題と解決策

  1. WebAssemblyとブラウザAPIの連携

    • 課題:クリップボード機能の実装で型の不一致が発生
    • 解決:web-sysクレートの適切な機能を有効化し、型の変換を正しく処理
  2. 状態管理とライフタイム

    • 課題:コンポーネントの状態管理でライフタイムエラーが発生
    • 解決:use_stateuse_mut_refを適切に使い分けて実装

セットアップと実行方法

  1. プロジェクトの作成:
cargo new --lib yew-pomodoro
cd yew-pomodoro
  1. 依存関係の追加(Cargo.toml):
[dependencies]
yew = { version = "0.21", features = ["csr"] }
gloo-timers = "0.3"
web-sys = { version = "0.3.64", features = ["console", "HtmlInputElement", "Window", "Navigator", "Clipboard"] }
wasm-bindgen = "0.2"
chrono = "0.4"
  1. アプリケーションの実行:
trunk serve

開発を通じて学んだこと

  1. WebAssemblyの基本概念

    • JavaScriptとの連携方法
    • パフォーマンスの最適化ポイント
  2. Rustの特徴的な機能

    • 所有権システム
    • 型安全性の恩恵
    • コンパイル時のエラーチェックの強力さ
  3. フロントエンド開発での新しい視点

    • パフォーマンスを意識したコンポーネント設計
    • WebAssemblyならではの制約と利点

所感

代休を使って新しい技術に触れる時間が作れたのは、とても良い経験になりました。特に、JavaScriptで書いているような処理をRustで書き直してみることで、言語の特性や設計の違いについて深く考える機会になりました。

WebAssemblyは確かにまだエコシステムが発展途上ですが、Yewのような成熟したフレームワークを使えば、実用的なアプリケーションを比較的スムーズに開発できることが分かりました。

ソースコード

プロジェクトのソースコードGitHubで公開しています。コメントや改善案があれば、お気軽にIssueやPull Requestを送ってください。

次のステップ

今回の学習を通じて、WebAssemblyの可能性を実感できました。次は以下のような発展的な内容に挑戦してみたいと考えています:

  1. Rustの非同期処理を活用した機能拡張
  2. WebAssemblyのパフォーマンス測定と最適化
  3. より大規模なアプリケーションでの実践

参考リンク

休暇を使って新しい技術に触れることで、普段の業務にも新しい視点を持ち込めそうです。皆さんも、空き時間を見つけて興味のある技術に触れてみてはいかがでしょうか。

読んだ書籍

[asin:B07RHB2MND:detail]