中級 data-structures

文字列処理の詳細

エンコーディングの不一致

これはWasmで最もよくあるパフォーマンスの落とし穴です:

Rust / Wasm JavaScript
エンコーディング UTF-8 UTF-16
ASCII文字あたりのバイト数 1 2
日本語文字あたりのバイト数 3 2
絵文字あたりのバイト数 4 4

文字列がWasm境界を越えるたびに、再エンコードが必要です:

JS文字列 (UTF-16) → UTF-8にエンコード → Wasmメモリにコピー → Rust &str
Rust String (UTF-8) → UTF-8からデコード → JS文字列を生成 (UTF-16)

このコピーは毎回発生します — 現在のWasmでは回避できません。

wasm-bindgenによる文字列処理

JS → Rust (&str)

#[wasm_bindgen]
pub fn process(input: &str) -> String {
    // inputはすでにWasmメモリ内のUTF-8
    // wasm-bindgenが呼び出し前にJS UTF-16からエンコード済み
    format!("Processed: {}", input)
    // String返却 → wasm-bindgenがUTF-8をデコード → JSがUTF-16文字列を取得
}

内部の動作:

  1. JSが TextEncoder.encode() を呼んでUTF-16 → UTF-8に変換
  2. UTF-8バイトをWasmリニアメモリにコピー
  3. ポインタ + 長さをRustに渡す
  4. Rustは有効な &str として参照する

Rust → JS (String)

  1. RustがUTF-8バイトをリニアメモリに書き込む
  2. wasm-bindgenが TextDecoder.decode() を呼んでUTF-8 → UTF-16に変換
  3. JSがネイティブ文字列を取得する

パフォーマンスコスト

操作                       時間 (1MB文字列)
─────────────────────────────────────────
JS → Rust (&str)          ~2ms (エンコード + コピー)
Rust → JS (String)        ~2ms (デコード + コピー)
Rust内部 (コピーなし)      ~0ms (ポインタのみ)

ルール: 文字列の境界越えを最小限にする。すべての文字列処理を片側で行う。

最適化戦略

1. バッチ処理 — アイテムごとに境界を越えない

// 悪い例: N回境界を越える
#[wasm_bindgen]
pub fn process_one(item: &str) -> String { /* ... */ }

// 良い例: 1回だけ境界を越える
#[wasm_bindgen]
pub fn process_all(items_json: &str) -> String {
    // すべてのアイテムをパース、処理、全結果を返す
    // 境界越えは合計2回のみ
}

2. 文字列の代わりに数値IDを使う

// 悪い例: 文字列をやり取りする
pub fn get_user_name(name: &str) -> String { /* ... */ }

// 良い例: IDを渡し、文字列はRust内に保持
pub fn create_user(name: &str) -> u32 { /* IDを返す */ }
pub fn get_user_name(id: u32) -> String { /* IDで検索 */ }

3. バイナリデータには &[u8] を使う

// バイナリデータを文字列としてエンコードしない
pub fn process_base64(data: &str) -> String { /* ... */ }

// 生バイトを渡す
pub fn process_bytes(data: &[u8]) -> Vec<u8> { /* ... */ }

4. キャパシティを事前確保する

// 悪い例: 多数の小さなアロケーション
let mut result = String::new();
for item in items {
    result.push_str(&format!("{},", item));
}

// 良い例: 1回のアロケーション
let mut result = String::with_capacity(items.len() * 20);
for item in items {
    result.push_str(&format!("{},", item));
}

Wasmでよくある文字列パターン

CSV/JSON処理

#[wasm_bindgen]
pub fn parse_csv(input: &str) -> JsValue {
    let rows: Vec<Vec<&str>> = input
        .lines()
        .map(|line| line.split(',').collect())
        .collect();
    serde_wasm_bindgen::to_value(&rows).unwrap()
}

テンプレートレンダリング

#[wasm_bindgen]
pub fn render_template(template: &str, name: &str, count: u32) -> String {
    template
        .replace("{{name}}", name)
        .replace("{{count}}", &count.to_string())
}

試してみよう

Run をクリックして、UTF-8エンコーディングの動作を確認しましょう — バイト長、マルチバイト文字、文字列の構築を体験できます。日本語文字がUTF-8では3バイトですが、JavaScriptのUTF-16では2バイトであることに注目してください。

試してみる

チャプタークイズ

すべての問題に正解してレッスンを完了しましょう