カテゴリ: Rust 更新日: 2026/02/25

RustのStringと&strの違いを完全ガイド!初心者でもわかる文字列処理と所有権

RustのStringと&strとは何か?2つの違いを初心者向けに解説
RustのStringと&strとは何か?2つの違いを初心者向けに解説

先生と生徒の会話形式で理解しよう

生徒

「Rustで文字列を扱おうとしたら、Stringと&strの二種類が出てきて混乱しています。何が違うんですか?」

先生

「Rustを始めたばかりの人が最初にぶつかる壁ですね。実はこれ、所有権(Ownership)と密接に関係しているんです。」

生徒

「所有権ですか……。どちらを使えばいいか、決まりはあるんでしょうか?」

先生

「簡単に言うと、Stringは自由に中身を書き換えられるデータで、&strはどこかにあるデータを覗き見ているだけの状態です。この違いをマスターすれば、Rustのメモリ安全性がより深く理解できますよ!」

1. Rustの所有権(Ownership)とは?

1. Rustの所有権(Ownership)とは?
1. Rustの所有権(Ownership)とは?

Rustの所有権(Ownership)は、値(データ)を「誰が持っているか」を明確にし、スコープ(有効範囲)と連動してメモリを安全に管理する仕組みです。C言語やC++のように手動でfreedeleteを呼ばなくても、Rustは所有権のルールに従って自動的にリソースを解放できます。

この所有権のルールは文字列処理においても非常に重要です。Rustには複数の文字列型が存在しますが、その中でも中心的な役割を果たすのが、所有権を持つ String と、所有権を持たずに借用する &str(文字列スライス)です。これらを適切に使い分けることで、効率的で安全なプログラムを書くことが可能になります。

2. String型の特徴:変更可能な所有者

2. String型の特徴:変更可能な所有者
2. String型の特徴:変更可能な所有者

String 型は、ヒープ領域にデータを保存し、そのデータの「所有権」を持つ型です。大きな特徴は、プログラムの実行中に中身を増やしたり、書き換えたりできることです。例えば、ユーザーからの入力を受け取ったり、複数の文字列を結合して新しい文章を作ったりする場合に最適です。

メモリの観点で見ると、String はサイズが可変であるため、必要に応じてメモリを再確保することができます。そのため、非常に柔軟ですが、メモリを管理するためのコストが多少かかります。Rustの基本ルールとして、String 型の変数がスコープを抜けると、その中身も自動的にメモリから消去されます。


fn main() {
    // String::from で新しく文字列を作成する(所有権を持つ)
    let mut my_string = String::from("Hello");
    
    // push_str で文字列を追加して書き換えることができる
    my_string.push_str(", Rust!");
    
    println!("{}", my_string);
}

3. &str型の特徴:覗き見るだけの参照

3. &str型の特徴:覗き見るだけの参照
3. &str型の特徴:覗き見るだけの参照

&str は「文字列スライス」と呼ばれ、どこか別の場所に保存されている文字列の一部(あるいは全部)を指し示している「参照」の型です。自分自身ではデータの所有権を持っていないため、中身を直接書き換えることはできません。本物の本(String)を借りて読んでいるだけ(&str)のような状態だと考えてください。

最も身近な &str は、コードの中に直接書く「文字列リテラル」です。これはプログラムの実行ファイル自体に含まれており、読み取り専用として扱われます。所有権を移動させる必要がないため、データのコピーが発生せず、処理が非常に高速であるというメリットがあります。


fn main() {
    // 文字列リテラルは &str 型になる
    let literal: &str = "固定の文字列";
    
    // 中身を書き換えることはできない
    // literal.push_str("無理です"); // エラーになります
    
    println!("表示のみ: {}", literal);
}

4. メモリ構造の違いをイメージしよう

4. メモリ構造の違いをイメージしよう
4. メモリ構造の違いをイメージしよう

String&str の最大の違いはメモリ上の扱いです。String は「ポインタ、長さ、容量」という情報を持ち、ヒープ領域にある実体データを指しています。一方、&str は「ポインタ、長さ」という情報だけを持っており、どこかにあるデータの一部を範囲指定して見ているだけです。

このため、&strString の一部を指すこともできますし、実行ファイル内の読み取り専用データを指すこともできます。この柔軟性こそが Rust の文字列処理のパワーの源ですが、初心者が混乱する原因でもあります。まずは「書き換えるなら String、見るだけなら &str」という原則を覚えましょう。

5. 関数の引数にはどちらを使うべきか

5. 関数の引数にはどちらを使うべきか
5. 関数の引数にはどちらを使うべきか

Rustの慣習として、関数の引数に文字列を渡す場合は、&str を使うことが推奨されています。なぜなら、&str を引数にすれば、String の参照も文字列リテラルも、どちらも同じように受け取ることができるからです。これにより、関数の汎用性が一気に高まります。

もし引数に String を指定してしまうと、呼び出し側は所有権を渡さなければならず、渡した後にその文字列を使えなくなってしまいます。特別な理由がない限り、読み取り目的の関数では &str を使い、呼び出し側が自由にデータを扱えるようにしておくのが Rust らしい設計です。


fn main() {
    let s = String::from("Rust入門");
    let l = "リテラル";

    // 両方の型を同じ関数で扱える
    print_message(&s); // String の参照を渡す
    print_message(l);  // &str をそのまま渡す
}

// 引数を &str にするのがベストプラクティス
fn print_message(message: &str) {
    println!("メッセージ: {}", message);
}

6. 型の変換:Stringから&strへ

6. 型の変換:Stringから&strへ
6. 型の変換:Stringから&strへ

String 型を &str 型に変換するのは非常に簡単です。変数の前に &(アンパサンド)を付けるだけで、Rustが自動的に文字列スライスとして扱ってくれます。これを「参照への強制(Deref coercion)」と呼びます。このため、String を持っていれば、いつでも &str として貸し出すことができます。

一方で、&str から String を作りたい場合は、明示的にデータをコピーして新しくメモリを確保する必要があります。これには to_string()String::from() を使います。この操作は新しいメモリ領域を作成するため、変換コストがかかることを意識しておきましょう。

7. 文字列の結合と所有権のパズル

7. 文字列の結合と所有権のパズル
7. 文字列の結合と所有権のパズル

複数の文字列をつなげる場合、Rustでは少し特殊な書き方をすることがあります。最も一般的なのは format! マクロを使う方法です。これを使うと、内部的に新しい String が生成されるため、所有権を気にせず直感的に文字列を組み立てることができます。

また、演算子 + を使う方法もありますが、これには「左側が String で、右側が &str でなければならない」というルールがあります。さらに、左側の String は所有権が移動してしまい、結合後は使えなくなるという落とし穴があります。初心者の方は、まずは安全で分かりやすい format! マクロから慣れていくのがおすすめです。


fn main() {
    let part1 = "プログラミング";
    let part2 = String::from("言語");

    // format! マクロなら所有権を維持したまま新しい String を作れる
    let result = format!("Rustは最高の{}です", part1);
    
    println!("{}", result);
    println!("part1もまだ使える: {}", part1);
    println!("part2もまだ使える: {}", part2);
}

8. 文字列スライスの範囲指定と注意点

8. 文字列スライスの範囲指定と注意点
8. 文字列スライスの範囲指定と注意点

&str の強力な機能の一つに、文字列の「一部だけ」を切り出す機能があります。例えば &s[0..5] のように範囲を指定することで、新しいメモリを使わずに特定の箇所だけを参照できます。これは非常に効率的ですが、Rust特有の注意点があります。

それは「文字の境界」です。Rustの文字列は UTF-8 でエンコードされているため、日本語のような多バイト文字の途中でスライスを切ろうとすると、実行時にプログラムがパニック(強制終了)してしまいます。初心者のうちは、文字列をバイト単位で切り刻むのではなく、標準ライブラリのメソッドを活用するようにしましょう。

9. なぜ文字列型が分かれているのか?

9. なぜ文字列型が分かれているのか?
9. なぜ文字列型が分かれているのか?

他の言語を経験していると「なぜ String だけで統一しないのか」と疑問に思うかもしれません。Rustがこのように型を分けている理由は、ひとえに「ゼロコスト抽象化」と「メモリ安全」を究極まで突き詰めるためです。どこにあるか分からないデータを常に書き換え可能にするのは、メモリ管理の上で非常に大きな負担になります。

読み取り専用のデータ(&str)と、動的に変わるデータ(String)を明確に区別することで、コンパイラは無駄なメモリ確保を極限まで減らすことができます。これは、家電製品のようなメモリが少ない環境から、超高速なサーバーまで、あらゆる場所で Rust が高いパフォーマンスを発揮するための知恵なのです。

10. 迷った時の使い分けチャート

10. 迷った時の使い分けチャート
10. 迷った時の使い分けチャート

最後に、実際の開発でどちらを使うべきか迷った時の判断基準を整理しましょう。まず、「データを自分で所有し、後で変更したり、関数の外へ持ち出したりしたいか?」を考えます。その答えがイエスなら String を選びます。逆に、関数の引数として受け取るだけだったり、固定の定数を定義したりする場合は &str を選びます。

Rustのエラーメッセージは非常に親切です。もし型を間違えていても「ここに &str を入れるべき場所に String を渡そうとしています」といった具体的な解決策を提示してくれます。最初は型パズルに戸惑うかもしれませんが、この二つの違いを意識することで、あなたの Rust スキルは飛躍的に向上するはずです!

文字列の扱いは Rust 学習の大きな一歩です。String&str の違いを理解することは、そのまま Rust のメモリ管理の思想を理解することに繋がります。一歩ずつコードを書いて、この強力な仕組みを自分のものにしていきましょう!

カテゴリの一覧へ
新着記事
New1
C++
C++のメンバアクセス演算子を完全解説!初心者でもわかる . → :: の使い方まとめ
New2
Rust
Rustの文字列を極める!&str(文字列スライス)の基本概念とString型との違い
New3
C++
C++のキャスト演算子を完全解説!dynamic_cast・static_cast・const_cast・reinterpret_castを初心者向けに説明
New4
C++
C++開発のIDE選びを完全ガイド!初心者でもわかるCLion・Eclipse CDT・Qt Creator比較
人気記事
No.1
Java&Spring記事人気No1
C++
C++の主要な実装をわかりやすく解説!GCC・Clang・MSVCの違いと特徴
No.2
Java&Spring記事人気No2
C言語
C言語を学ぶ初心者におすすめの環境構築手順【2025年版】
No.3
Java&Spring記事人気No3
C言語
C言語のソースコードとヘッダファイルの役割とは?初心者向けにわかりやすく解説!
No.4
Java&Spring記事人気No4
C言語
Visual Studio CodeでC言語を実行する方法【拡張機能の設定と実行手順】
No.5
Java&Spring記事人気No5
C言語
C言語をオンラインで実行できる便利なコンパイラサービスまとめ【初心者向け】
No.6
Java&Spring記事人気No6
C言語
C言語開発でよく使われるエディタとIDEランキング【初心者向け完全ガイド】
No.7
Java&Spring記事人気No7
C++
C++リンカとコンパイラのオプション設定を完全ガイド!初心者にもわかる開発環境の基礎
No.8
Java&Spring記事人気No8
C言語
C言語の列挙型(enum)の使い方を完全ガイド!初心者でもわかる基本操作