Rustでプログラミングをする際、初心者がよく混乱しやすい概念のひとつがStringと&strです。どちらも文字列を扱うための型ですが、メモリ管理の仕組み、変更可能性(mutability)、利用範囲において明確な違いがあります。
この2つの特徴と正しい使い分けを理解することで、コードをより効率的かつ安全に書けるだけでなく、Rustの強みであるメモリ安全性を最大限に活かすことができます。
本記事では、Stringと&strの違いを詳しく解説し、それぞれをどのような場面で使うべきかについて具体的なガイドラインを紹介します。
RustのStringについて
Stringとは?
RustにおけるStringは、サイズを変更でき、かつデータの所有権を持つ文字列型です。
簡単に言えば、自分自身でデータを「所有」する文字列 であり、他の多くの言語における文字列型(例: C++のstd::stringやJavaのString)と似ています。
>>>関連記事:
Stringはヒープ領域(動的メモリ)に格納されます。
そのため、必要に応じてサイズを拡張・縮小することが可能です。例えば、文字を追加したり、別の文字列を結合する場合に便利です。
- Rustの例
fn main() {
let mut my_string = String::new();
my_string.push_str("Hello, ");
my_string.push('R');
my_string.push('u');
my_string.push('s');
my_string.push('t');
println!("{}", my_string);
}
出力: "Hello, Rust"
Stringの主な特徴
RustのString型には、安全かつ柔軟な文字列操作を可能にする以下の主な特徴があります。
- データの所有権: Stringは文字列データの所有権(Ownership)を持ち、メモリを自身で管理します。このため、変数がスコープから外れると、Rustは自動的に割り当てられたメモリを解放し、メモリリークを防ぎます。
- ヒープに格納: Stringの文字列データはヒープ(Heap)に動的に割り当てられます。これにより、文字列のサイズを柔軟に変更できます。Stringは、ヒープ上のデータへのポインタ、データの長さ(
len
)、そして容量(capacity
)という3つの情報を持つ軽量な構造体です。 - 可変性(Mutable): Stringは初期化後に変更可能(Mutable)です。文字を追加(push)したり、文字列を連結(push_str または +)したり、内容を変更することができます。
- 豊富なメソッド: String型には、
len()
(長さを取得)、is_empty()
(空かどうかを判定)、contains()
(部分文字列の存在をチェック)、replace()
(置換)など、文字列を操作するための便利なメソッドが多数用意されています。
>>>関連記事:

- Stringの内部構造の例:
fn main() {
let s = String::from("Hello");
println!("len = {}, capacity = {}", s.len(), s.capacity());
}
「rust string」を使う上での注意点
Stringは多くのメリットを提供しますが、その特性を理解し、適切に使用しないとパフォーマンス上の問題を引き起こす可能性があります。以下に、特に注意すべき点を挙げます。
- メモリコスト: Stringのデータはヒープに格納されるため、&strに比べて作成や変更にコストがかかる場合があります。これは、文字列の結合などの操作を行う際に、メモリの割り当てや解放、データのコピーが必要になるためです。
fn main() {
let mut s = String::from("Hello");
// この操作は、既存の容量が不足している場合、ヒープ上の再割り当てを引き起こす可能性があります。
s.push_str(" world");
println!("{}", s);
}
&str
への変換:Rustでは、関数の引数にStringではなく&strを使用することが推奨されます。これにより、所有権の移動を防ぐことができます。.Stringは、参照演算子&
や.as_str()
メソッドを使うことで、簡単に&strに変換できます。
fn main() {
// &strを引数に取る関数
fn greet(name: &str) {
println!("Hello, {}!", name);
}
let my_string = String::from("Rust");
let my_literal = "World";
// `String`を参照として渡す
greet(&my_string);
// `&str`(文字列リテラル)を直接渡す
greet(my_literal);
}
+
演算子の制限: Rustの+
演算子を使った文字列結合は直感的ではありません。左側のStringの所有権を移動させてしまうため、結合後にその変数を再利用することはできません。代わりに、format!
マクロやpush_str
メソッドの使用が推奨されます。
fn main() {
let s1 = String::from("Hello, ");
let s2 = String::from("Rust");
let s3 = s1 + &s2;
// println!("{}", s1); // s1は所有権が移動したため、この行でコンパイルエラーが発生します。
println!("{}", s3);
}
→ 解決策:
fn main() {
let s1 = String::from("Hello, ");
let s2 = String::from("Rust");
let s3 = format!("{}{}", s1, s2);
println!("{}", s3);
println!("s1はまだ利用可能です: {}", s1); // s1とs2はそのまま利用できます。
}
Rustの&strについて
&strについて
&strは、Stringとは異なり、文字列スライス(string slice)と呼ばれるものです。これは、データの所有権を持たずに、既存の文字列を「参照」するビューとして機能します。
&strは、メモリ上のデータの位置を示すポインタと、そのデータの長さという2つの情報から構成されています。このため、&strは「ファットポインタ」(fat pointer)とも呼ばれます。
&strの主な特徴
- 軽量かつ高効率
関数に&strを渡す際、Rustは文字列全体のデータをコピーするのではなく、データへのポインタと長さをコピーするだけです。これにより、メモリを節約し、特に大きな文字列を扱う際のパフォーマンスが向上します。
- 不変性
&strは、読み取り専用の「ビュー」です。この参照を通じて元の文字列の内容を変更することはできません。
- 所有権と借用
&strは、所有権を持たず、データを「借用」する型です。そのため、参照先のデータよりも長く存在することはできません。この「ライフタイム」(生存期間)のルールにより、Rustはデータが既に解放された後に参照する「ダングリングポインタ」のバグを防ぎます。

「rust str」を使う上での注意点
&strは非常に効率的ですが、その「参照」としての性質を理解することが重要です。以下に、&strを安全かつ効果的に使用するためのポイントをまとめます。
- ライフタイムの制限
&strは、参照しているデータのライフタイム(生存期間)よりも長く存在することはできません。Rustのコンパイラは、このルールを厳密にチェックします。関数内で作成したStringへの&str参照を返そうとすると、コンパイルエラーになります。これは、関数が終了すると元のStringが破棄されてしまうため、参照が無効になるのを防ぐためです。
fn invalid_str() -> &str {
let s = String::from("hello");
// エラー: 変数sは関数終了時に破棄されます。
// そのため、この参照は無効となります。
&s
}
- 不変性
Stringは可変な型なので、文字の追加、削除、変更といった操作が必要な場合は、Stringを使用しなければなりません。&strからStringへの変換は、.to_string()
メソッドやString::from()
を使って簡単に行えます。
fn main() {
let slice: &str = "hello";
let owned: String = slice.to_string();
println!("{}", owned);
}
- 最適化
関数の引数にはStringではなく&strを使用することが推奨されます。これにより、関数の柔軟性が高まり、所有権の移動を防ぐことができます。&strを引数に取る関数は、Stringの参照(&String)と文字列リテラルの両方を受け取ることが可能です。
fn main() {
fn greet(name: &str) {
println!("Hello, {}!", name);
}
let s1 = String::from("Rust");
let s2 = "World";
greet(&s1); // Stringを参照として渡す
greet(s2); // 文字列リテラルを直接渡す
}
Stringとstr:違い
Rustにおいて、Stringと&strはどちらも文字列を扱いますが、その動作と用途は根本的に異なります。この違いを理解することが、適切な場面で最適な型を選択するために不可欠です。
Stringと&strの比較表
特徴 | String | &str |
データの所有権 | 所有者(Owned)– メモリを自身で管理 | 借用者(Borrowed)– 既存のデータを参照するのみ |
メモリ上の位置 | ヒープ (データ本体) | スタック (ポインタと長さのみ) |
可変性 | 可変(Mutable)– 内容の追加や編集が可能 | 不変(Immutable)– 読み取り専用 |
長さ | 可変 | 固定 |
使用目的 | 所有権を持つ必要があり、内容を変更する可能性のある文字列 | データの高速な読み取りや、所有権を移動せずに関数の引数として渡す場合 |
詳細な解説
- 所有権とメモリ
Stringは、ヒープ上の文字列データを格納する「箱」のようなものです。メモリの確保と解放という管理責任を自身で負います。
一方、&strは、既存のデータの場所を示すだけの「タグ」のようなものです。メモリを管理せず、一時的にデータを借りて使用します。 - 柔軟性
Stringは、そのサイズと内容を自由に変更できるため、文字列を頻繁に操作する必要がある場合に非常に適しています。
一方、&strは軽量で、読み取り専用のアクセスにのみ使用されます。 - パフォーマンスと最適な使い分け
Stringは、動的なメモリ割り当てと管理が必要となるため、&strに比べてコストがかかります。
&strはメモリ効率が良く、特に関数の引数として渡す場合に高速です。
String
は、所有権を持ち、文字列を変更または長期間保存する必要がある場合に使用します。&str
は、データを参照し、読み取るだけで良い場合や、所有権を移動させたくない場合に使用します。
RustにおけるStringと&strの関係
Stringと&strは、完全に独立したデータ型ではなく、Rustの所有権(Ownership)システムを通じて密接に結びついた、同じ文字列の異なる側面を表しています。この関係性は、主に以下の2つの点に集約されます。
&strはStringの「参照」
すべてのStringは、参照演算子(&
)や.as_str()
メソッドを使うことで、&strに変換できます。
&strを使用すると、Stringの所有権を移動させずに、そのデータを「借用」できます。これは、読み取り専用のアクセスを必要とする関数にStringを渡す際に非常に便利です。コストのかかるデータコピーを避けつつ、元のStringの所有権を保持できます。
例:
fn main() {
let my_string = String::from("Hello, World!");
// Rustの Deref Coercion により、&my_string を &str を引数に取る関数に渡すことができます。
print_slice(&my_string);
}
fn print_slice(slice: &str) {
println!("{}", slice);
}
Stringは&strの「所有者」
- 逆に、&str から String を作成することも可能です。これは、文字列を変更したり、データの所有権を保持したりする必要がある場合によく行われます。
- この変換は、
.to_string()
やString::from()
などのメソッドを使って簡単に行えます。
例:
fn main() {
let my_slice: &str = "Hello, Rust";
// &str から所有権を持つ String を作成
let my_string: String = my_slice.to_string();
// これで、この文字列を自由に編集できるようになります
println!("{}", my_string);
}
この関係性を理解することで、Rust開発者は、読み取り専用の軽量な &str と、所有権を持つ動的な String を柔軟に使い分け、パフォーマンスとメモリ管理を最適化できます。
RustにおけるStringとstr:どちらをいつ使うべきか?
Stringと&strの特性と関係性を理解した上で、最も重要なのは「実践でどちらを使うべきか」を判断することです。
&strを使うべき時
一言で言えば、可能な限り&strを優先するのがベストプラクティスです。
- 柔軟性と効率性: &strは、関数の引数として最適な選択です。参照であるため、Stringと文字列リテラル(&str型)の両方を受け入れることができ、データのコピーを避けられるため、コードがより柔軟かつ効率的になります。
- 読み取り専用のアクセス: 文字列を読み取るだけで、変更する意図がない場合は、新しいStringを作成する無駄なコストを避けるために&strを使用します。
Stringを使うべき時
Stringは、所有権と可変性が必要な場合に使用します。
- 新しい文字列の作成: 既存の変数から新しい文字列を生成する場合にStringを使用します。例えば、
format!
マクロを使って複数の変数を結合する際などです。 - 内容の変更: 文字の追加、結合、内容の変更など、文字列を編集する必要がある場合は、Stringが適切なデータ型です。
- 所有権の移動: データを別の関数に渡し、その関数がそのデータの所有者となる必要がある場合は、Stringが唯一の選択肢となります。
まとめ
Rustにおいて、Stringと&str は単なる文字列型の違いではなく、所有権と借用というRustの根幹に関わる重要な概念を体現しています。
- Stringは「所有する」型であり、データをヒープに保持し、自由に編集・拡張できる柔軟さを持ちます。
- &strは「借用する」型であり、既存の文字列データを効率的に参照し、読み取り専用で扱うことができます。
この二つの型は相互に変換可能であり、状況に応じて使い分けることで、効率的かつ安全な文字列操作 が実現できます。
実務的な指針としては、関数の引数では&strを優先し、内部で編集や所有が必要な場合にStringを用いるのがベストプラクティスです。
Stringと&str の関係性を理解し適切に使い分けることは、Rustで堅牢かつ高性能なコードを書くための第一歩です。
Rustを活用して効率的かつコスト最適化されたシステム開発をお考えであれば、ぜひRelipaにご相談ください。
Relipaは、日本市場および国際市場向けに9年以上のプロジェクト実績を持ち、Web3・AI・Blockchainに精通したRustエンジニアチームが在籍しています。アイデア段階から完成品まで、御社のパートナーとして伴走いたします。