はじめに
Rustを学び始めたとき、多くの人が最初につまずくポイントの一つが 文字列型 です。他の言語では「文字列」は一種類で済むことが多いですが、Rustには String と &str という二種類の文字列型があります。
この記事では、それぞれの型が何者で、どう使い分けるのかを丁寧に解説します。
StringとはどんなやつなのかTypeScript
String — 所有権を持つ文字列
String はヒープに確保された、所有権を持つ 可変な文字列型です。実行時に中身を変更したり、長さを伸ばしたりすることができます。
fn main() {
let mut s = String::from("hello");
s.push_str(", world!"); // 文字列を追加できる
println!("{}", s); // hello, world!
}
String::from() や .to_string() を使って生成します。内部的にはバイト列(Vec<u8>)として管理されており、ヒープ上にデータが置かれます。
&str — 文字列スライス(借用)
&str は文字列データへの 参照(スライス) です。実体はどこかに存在する文字列の一部または全部を指すポインタと長さのペアです。
fn main() {
let s: &str = "hello, world!"; // 文字列リテラルは &str
println!("{}", s);
}
文字列リテラル("..." で書いたもの)はコンパイル時にバイナリに埋め込まれ、&'static str 型になります。変更はできませんが、非常に軽量です。
二つの違いを図で整理する
| 特徴 | String | &str |
|---|---|---|
| 所有権 | あり | なし(借用) |
| 変更 | 可能 | 不可 |
| メモリ | ヒープ | ヒープ or バイナリ領域 |
| 生成コスト | やや高い | 低い |
| 典型的な使い方 | 動的に文字列を組み立てる | 読み取りのみの参照 |
変換方法を覚えよう
&str → String
fn main() {
let s1: &str = "hello";
// 方法1: String::from()
let s2: String = String::from(s1);
// 方法2: to_string()
let s3: String = s1.to_string();
// 方法3: to_owned()
let s4: String = s1.to_owned();
println!("{} {} {}", s2, s3, s4);
}
String → &str
String から &str を得るには、& をつけるだけです。Rustの Deref強制変換 により自動的に &str として扱われます。
fn main() {
let s: String = String::from("hello");
// &String は自動的に &str に変換される
let slice: &str = &s;
println!("{}", slice);
}
関数の引数にはどちらを使う?
文字列を読み取るだけの関数には &str を使う のが慣用的です。&str を引数にすると、String も &str もどちらも渡せて汎用性が高まります。
// &str を引数にすると両方受け取れる
fn greet(name: &str) {
println!("こんにちは、{}さん!", name);
}
fn main() {
let owned = String::from("Alice");
let literal = "Bob";
greet(&owned); // String を &str として渡す
greet(literal); // &str をそのまま渡す
}
逆に String を引数にすると、&str リテラルを渡す際に .to_string() が必要になり、不要なヒープ確保が発生します。
実践例:文字列を組み立てる
動的に文字列を構築したい場合は String を使います。
fn build_greeting(first: &str, last: &str) -> String {
let mut result = String::new();
result.push_str("こんにちは、");
result.push_str(first);
result.push(' ');
result.push_str(last);
result.push_str("さん!");
result
}
fn main() {
let greeting = build_greeting("山田", "太郎");
println!("{}", greeting);
// こんにちは、山田 太郎さん!
}
format! マクロを使うとさらに簡潔に書けます。
fn build_greeting(first: &str, last: &str) -> String {
format!("こんにちは、{} {}さん!", first, last)
}
よくあるエラーと対処法
「expected &str, found String」
fn print_message(msg: &str) {
println!("{}", msg);
}
fn main() {
let s = String::from("hello");
print_message(s); // エラー!
// ↓ & をつけて参照として渡す
print_message(&s); // OK
}
String を &str が期待される場所に渡すときは & を忘れずに。
まとめ
Stringは所有権を持つ可変な文字列。動的に値を組み立てるときに使う&strは文字列への借用(スライス)。読み取り専用で軽量- 関数の引数には汎用性の高い
&strを使うのが基本 String→&strは&をつけるだけ、逆は.to_string()やString::from()を使う
文字列の扱いはRust初心者が最初に戸惑うポイントですが、「所有権があるかどうか」という観点で考えると自然に使い分けられるようになります。ぜひ手を動かして試してみてください!