RustのエラーE0369を解決する:なぜ&strを「+」で連結できないのか

beginner🦀 Rust2026-06-09| Rust (全バージョン), Cargo, すべてのOS (Linux, macOS, Windows)

Error Message

error[E0369]: binary operation `+` cannot be applied to type `&str`
#rust#文字列結合#エラーe0369#rust初心者

エラーのシナリオ

JavaScript、Python、Javaの経験があるなら、2つの文字列を+演算子で結合することはごく自然に感じられるでしょう。しかし、Rustはメモリの扱いに対して非常に慎重です。おなじみの構文を使って2つの文字列リテラル(&str)を連結しようとすると、おそらく最初に壁にぶつかるはずです。

コンパイラの逆鱗に触れる、よくあるスニペットを見てみましょう:

fn main() {
    let hello = "Hello, ";
    let world = "world!";
    
    // これはコンパイルに失敗します
    let greeting = hello + world;
    
    println!("{}", greeting);
}

cargo buildを実行すると、次のようなエラーメッセージが表示されます:

error[E0369]: binary operation `+` cannot be applied to type `&str`
 --> src/main.rs:6:26
  |
6 |     let greeting = hello + world;
  |                    ----- ^ ----- &str
  |                    |
  |                    &str

なぜRustはこれを拒否するのか

修正方法を理解するには、&strが実際には何であるかを知る必要があります。Rustにおいて、&strは文字列スライスです。これは本質的に、64ビットシステムでは16バイトの構造体であり、UTF-8バイト列へのポインタと長さで構成されています。それはメモリ(多くの場合、読み取り専用メモリ)への「ビュー」に過ぎないため、サイズを拡張することはできません。

Rustの+演算子は、Addトレイトによって提供されています。標準ライブラリはこのトレイトをString + &strに対してのみ実装しており、&str + &strに対しては実装していません。その実装がどのように機能するか、簡略化したものを見てみましょう:

impl Add<&str> for String {
    type Output = String;
    fn add(mut self, other: &str) -> String {
        self.push_str(other);
        self
    }
}

ここで重要な詳細が明らかになります。左辺は所有権を持つStringである必要があります。この操作は実際にはそのStringを消費し、バッファに新しいテキストを追加して、それを返します。スライス(&str)は自身のバッファを所有していないため、追加の文字を格納する場所がありません。

実践的な解決策

1. 最初の文字列を所有権のあるStringに変換する

最も手っ取り早い修正方法は、最初のスライスをヒープに割り当てられたStringに変換することです。これは.to_string()String::from()を使って行えます。これにより、拡張可能なバッファが作成されます。

fn main() {
    let hello = "Hello, ";
    let world = "world!";
    
    // 最初の部分をStringに変換します。2番目の部分はスライスのままで構いません
    let greeting = hello.to_string() + world;
    
    println!("{}", greeting);
}

ここでのhelloはリテラルであったことに注意してください。もしhelloがすでに所有権のあるString変数であった場合、+を使用するとその変数はムーブされるため、コードの後半で元の変数を使用することはできなくなります。

2. format! マクロを使用する(最もクリーンな方法)

複数の変数を組み合わせたり、テキストと数値を混ぜたりする必要がある場合、format!が最適です。+演算子を連結するよりもはるかに読みやすく、型変換も自動的に処理してくれます。

fn main() {
    let user = "Alice";
    let id = 42;
    
    // format! は &str と整数を簡単に処理できます
    let message = format!("User: {}, ID: {}", user, id);
    
    println!("{}", message);
}

format!は実行時のテンプレート解析のため、手動での連結よりもわずかに遅くなりますが、ほとんどのアプリケーションにおいてその差は無視できる程度です。

3. パフォーマンス向上のために push_str を使用する

すでに可変(mutable)なStringがある場合は、+で新しい文字列を作成しないでください。push_strを使用しましょう。これは既存のバッファをその場で変更するため、ループ内では大幅に効率的です。

fn main() {
    let mut buffer = String::with_capacity(50);
    buffer.push_str("First ");
    buffer.push_str("Second");
    
    println!("{}", buffer);
}

4. 文字列のコレクションを結合する

文字列のリストやベクタを扱う場合は、joinメソッドが慣用的な選択です。項目間の区切り文字を自動的に処理してくれます。

fn main() {
    let words = vec!["Rust", "is", "fast"];
    let sentence = words.join(" ");
    
    assert_eq!(sentence, "Rust is fast");
}

修正を確認する方法

これらの方法のいずれかを適用した後、cargo checkで動作を確認してください。これはフルビルドよりもはるかに高速です。コンパイラがエラーを出さなければ、型は正しく整合しています。方法1を使用した場合、もしそれが所有権のあるStringであれば、+演算子がその所有権を奪うため、同じ変数を再度使用しようとしていないか再確認してください。

パフォーマンスに関する考慮事項

- **事前割り当て:** 1MBの文字列を作成することがわかっている場合は、`String::with_capacity(1_000_000)`を使用してください。これにより、バッファがいっぱいになるたびにデータを再コピーする必要がなくなります。
- **メモリスライス:** 可能な限り、データは`&str`として保持してください。テキストを実際に変更する必要がある場合や、所有権を必要とする構造体に格納する場合にのみ、`String`に変換してください。
- **ループを避ける:** 大きなループの中で`s = s + "more"`を決して使用しないでください。これはイテレーションごとに新しいメモリ割り当てを行うため、計算量がO(n²)になってしまいます。

Related Error Notes