🦀

RustとWebAssemblyでQRコードジェネレーターを作る

2025/03/05に公開

はじめに

モチベーション

なぜこのプロジェクトを作ろうと思ったかというと、現状のQRコードを出すライブラリがどれも帯に短しタスキに長しと思ったからです。
どれも基本的な機能は揃えていますが、ビルドパフォーマンスや画像の埋め込みなどに課題があると感じています。
OSSにコミットをして、改善を図ることも考えましたが、コツコツ自分で1から作ることでRustやwasmについて詳しくなりたいと思ったので、自分のプロジェクトとしてスタートすることにしました。

今回の記事ではやったことをざざっと振り返りながら書いているので、不明な点などはコメントをいただけると嬉しいです。

WebAssemblyとは

WebAssembly(略称:WASM)は、モダンなWebブラウザで動作する低レベルのバイナリフォーマットです。以下の特徴があります。

主な特徴

高速実行: ネイティブに近いパフォーマンスを実現
型安全: 静的型付け言語のような安全性を提供
クロスプラットフォーム: 主要なブラウザで動作
言語非依存: C++、Rust、Go など様々な言語からコンパイル可能
JavaScriptとの関係
JavaScriptと共存・連携が可能
JavaScriptからWASMの関数を呼び出せる
WASMからJavaScriptのAPIにアクセス可能

用途例

画像・動画処理
ゲームエンジン
暗号化処理
数値計算

このプロジェクトでは、RustコードをWebAssemblyにコンパイルすることで、ブラウザ上で高速なQRコード生成を実現しています。

開発環境のセットアップ

必要なツールのインストール

# Rustのインストール
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

# wasm-packのインストール
cargo install wasm-pack

プロジェクトの作成

# 新規プロジェクト作成
cargo new wasm-qrcode-gen --lib
cd wasm-qrcode-gen

Cargo.tomlの設定

[package]
name = "wasm-qrcode-gen"
version = "0.1.0"
edition = "2021"

[lib]
crate-type = ["cdylib", "rlib"]  # WebAssembly用に必要

[dependencies]
# WASM関連
wasm-bindgen = "0.2"     # JavaScriptとの連携用
base64 = "0.21"          # Base64エンコーディング用

# QRコード生成関連
qrcode-generator = "4.1"  # QRコード生成ライブラリ

プロジェクト構造

wasm-qrcode-gen/
├── Cargo.toml
├── src/
│   └── lib.rs          # メインのライブラリコード
├── www/
│   └── index.html      # Webフロントエンド
└── examples/
    └── generate_qr.rs  # 実行例

ビルドと実行

# WASMにビルド
wasm-pack build --target web

# ローカルサーバーで実行
python3 -m http.server

QRコード生成の実装

Rustでのコア機能の実装

use wasm_bindgen::prelude::*;
use qrcode_generator::QrCodeEcc;
use base64::{Engine as _, engine::general_purpose::STANDARD};

#[wasm_bindgen]
pub fn generate_qr_code(data: &str, size: u32) -> String {
    // QRコードをSVGとして生成
    let qr = qrcode_generator::to_svg_to_string(
        data,                    // エンコードするデータ
        QrCodeEcc::Low,         // エラー訂正レベル
        size as usize,          // QRコードのサイズ
        None::<&str>            // 追加オプション
    ).unwrap();

    // SVGをBase64エンコード
    let base64_encoded = STANDARD.encode(qr);
    
    // データURIスキーマとして返す
    format!("data:image/svg+xml;base64,{}", base64_encoded)
}

実装のポイント

  1. WASM対応
    #[wasm_bindgen]属性で関数をエクスポート
    JavaScriptから呼び出し可能な型を使用
  2. QRコード生成
    qrcode-generatorクレートを使用
    SVG形式で出力
    エラー訂正レベルはLowを採用
  3. ブラウザ表示対応
    SVGをBase64エンコード
    データURIスキーマ形式で返却
    <img>タグで直接表示可能

使用例

<script type="module">
    import init, { generate_qr_code } from './pkg/wasm_qrcode_gen.js';

    async function run() {
        await init();
        const qrCode = generate_qr_code("https://www.example.com", 256);
        document.getElementById('qr-code').src = qrCode;
    }

    run();
</script>

WebAssemblyへの移行

wasm-bindgenの役割と使い方

  • RustとJavaScript間の橋渡し役
  • 型変換の自動化
  • メモリ管理の支援
  • JavaScriptのAPIへのアクセス提供
// wasm-bindgenの基本的な使用例
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub fn example_function(input: &str) -> String {
    // Rustの文字列型とJavaScriptの文字列型の自動変換
    format!("Hello, {}", input)
}

Rustコードのエクスポート

// 関数のエクスポート
#[wasm_bindgen]
pub fn generate_qr_code(data: &str, size: u32) -> String {
    // 実装
}

// 構造体のエクスポート
#[wasm_bindgen]
pub struct QrOptions {
    size: u32,
    error_level: String,
}

// メソッドのエクスポート
#[wasm_bindgen]
impl QrOptions {
    #[wasm_bindgen(constructor)]
    pub fn new(size: u32) -> Self {
        Self {
            size,
            error_level: "L".to_string(),
        }
    }
}

JavaScript/TypeScriptとの連携

// TypeScriptでの利用例
import init, { generate_qr_code, QrOptions } from './pkg/wasm_qrcode_gen';

async function run() {
    // WAMSモジュールの初期化
    await init();
    
    // 関数の呼び出し
    const qrCode = generate_qr_code("https://example.com", 256);
    
    // クラスの利用
    const options = new QrOptions(256);
}

メモリ管理の考慮点

自動メモリ管理

#[wasm_bindgen]
pub fn process_data(data: &str) -> String {
    // メモリは自動的に解放される
    let result = expensive_operation(data);
    result
}

明示的なメモリ管理

#[wasm_bindgen]
pub struct LargeData {
    buffer: Vec<u8>,
}

#[wasm_bindgen]
impl LargeData {
    // メモリを確保
    #[wasm_bindgen(constructor)]
    pub fn new() -> Self {
        Self {
            buffer: Vec::new(),
        }
    }

    // 明示的な解放
    #[wasm_bindgen]
    pub fn free(self) {
        drop(self);
    }
}

重要な考慮点

  1. 文字列とメモリ
    RustとJavaScript間の文字列変換は自動
    大きなデータの場合はメモリ使用量に注意
  2. リソース管理
    WASMのメモリは線形メモリ
    大きなデータ構造は明示的な解放を検討
  3. パフォーマンス
    データのコピーを最小限に
    必要な場合のみメモリ確保

パフォーマンスと最適化

WebAssemblyを選択する利点

wasmを利用する利点はいくつかありますが今回強いて挙げるとするとこちらのようなことが挙げられます。
とはいえ、今回のプロジェクトはwasmを使う技術的優位性からwasmを選んでいるわけではなく、wasmを利用したいが先に来ているので当てはまらないものもいくつかあります。
特に言語的な特徴などは、今回の利用の目的には当てはまりません。
しかし、wasmを使おうという技術選定のときにこのドキュメントが約にたったら良いなと思ったため書き残しておきます。

パフォーマンス

画像処理の高速化
メモリ効率の向上
安定した実行速度

セキュリティ

ソースコードの保護
型安全性の確保
メモリ安全性の保証

言語の特徴活用

Rustの所有権システム
静的型付けの恩恵
豊富なエコシステム

最適化のポイント

メモリ最適化

// メモリ効率の良いコード例
#[wasm_bindgen]
pub fn generate_qr_code(data: &str, size: u32) -> String {
    // 必要最小限のメモリ割り当て
    let qr = qrcode_generator::to_svg_to_string(
        data,
        QrCodeEcc::Low,
        size as usize,
        None::<&str>
    ).unwrap();
    
    // 効率的なエンコーディング
    STANDARD.encode(qr)
}

処理の最適化

  • バッファリングの適切な使用
  • 不要なデータコピーの削減
  • キャッシュの効果的な活用

デプロイと配布

ビルドプロセス
成果物の配布方法
npmパッケージとしての公開手順

まとめと発展

プロジェクトの振り返り

今回はかなりAIにも力を借りて開発を行いました。
Rustの出すエラーを解消することがメインの使い所でしたが、かなり助けられました。

また、今回紹介したものは最低限の機能を満たしただけでまだまだ開発の余地がかなりあります。
wasmを触ってみよう、自分の不満をなんとか解消してみる一歩目を踏み出して見ようという点においては概ねやりたいことが満たせたと感じました。

今後の改善点

ざっと列挙してみますが、かなりありますね。
今後も継続して開発を進めていきたいです。

機能の拡張

  • QRコードのカスタマイズオプション
  • エラー訂正レベルの選択機能
  • QRコードの色変更機能
  • 背景色の設定機能
  • 出力形式の追加
  • PNG形式のサポート
  • JPEGフォーマットのサポート
  • データURIスキーマ以外の出力オプション

パフォーマンス最適化

  • メモリ使用量の最適化
  • 大きなQRコード生成時のメモリ管理改善
  • バッファサイズの最適化
  • 処理速度の向上
  • 並列処理の導入
  • キャッシュ機構の実装

ユーザビリティの向上

  • エラーハンドリングの強化
  • より詳細なエラーメッセージ
  • エラー回復機能の実装
  • TypeScript型定義の改善
  • より詳細な型情報の提供
  • ドキュメンテーションの充実

Web対応の強化

  • Progressive Web App (PWA)対応
  • オフライン動作のサポート
  • インストール可能なアプリケーション化
  • モバイル対応の改善
  • レスポンシブデザインの強化
  • タッチ操作の最適化

テストとドキュメント

  • テストカバレッジの向上
  • ユニットテストの追加
  • E2Eテストの実装
  • ドキュメントの充実
  • API仕様書の作成
  • 使用例の追加
  • チュートリアルの作成

配布と連携

  • npmパッケージとしての公開
  • パッケージ管理の整備
  • バージョニング戦略の確立
  • 他フレームワークとの連携
  • React/Vue.js用コンポーネント
  • Angular用モジュール

Discussion