← レッスン一覧に戻る レッスン 22 / 28
中級 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文字列を取得
}内部の動作:
- JSが
TextEncoder.encode()を呼んでUTF-16 → UTF-8に変換 - UTF-8バイトをWasmリニアメモリにコピー
- ポインタ + 長さをRustに渡す
- Rustは有効な
&strとして参照する
Rust → JS (String)
- RustがUTF-8バイトをリニアメモリに書き込む
- wasm-bindgenが
TextDecoder.decode()を呼んでUTF-8 → UTF-16に変換 - 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バイトであることに注目してください。
試してみる
チャプタークイズ
すべての問題に正解してレッスンを完了しましょう