RustのStringと&strの違い:使い分けと変換方法まとめ

スポンサーリンク

RustのStringと&strの違い:使い分けと変換方法まとめ

はじめに

Rustには文字列を扱う型が主に2つあります。String&str です。どちらも文字列ですが、所有権とメモリの扱いが異なります。最初はどちらを使えばいいか迷いやすいポイントなので、違いと使い分けをまとめます。


String と &str の基本的な違い

String &str
所有権 あり(ヒープ上) なし(借用)
変更 可能 不可能
サイズ 実行時に変わる 固定
主な用途 文字列を所有・変更したい 文字列を参照するだけ
let s1: String = String::from("hello");  // ヒープ上に確保
let s2: &str = "hello";                  // プログラムバイナリに埋め込まれた文字列への参照

String

String はヒープ上にデータを持つ、所有権のある文字列型です。内容を変更できます。

let mut s = String::from("hello");
s.push(' ');           // 文字を追記
s.push_str("world");   // 文字列を追記
println!("{}", s);     // hello world

String の生成方法

let s1 = String::new();                    // 空文字列
let s2 = String::from("hello");            // 文字列リテラルから
let s3 = "hello".to_string();             // &str から変換
let s4 = format!("{} {}", "hello", "world"); // フォーマット

&str(文字列スライス)

&str は文字列データへの参照です。所有権を持たず、変更もできません。

let s: &str = "hello";   // 文字列リテラル('static ライフタイム)

let owned = String::from("hello world");
let slice: &str = &owned[0..5];   // String の一部への参照 → "hello"

文字列リテラル "hello" の型は &'static str(プログラムが終わるまで有効)です。


変換方法

&str → String

let s: &str = "hello";

let owned1 = s.to_string();
let owned2 = String::from(s);
let owned3 = s.to_owned();

String → &str

let s = String::from("hello");

let slice1: &str = &s;         // 自動的に変換(Deref)
let slice2: &str = s.as_str(); // 明示的に変換
let slice3: &str = &s[..];     // スライス構文

関数の引数にはどちらを使うか

文字列を受け取るだけで変更しない関数には &str を使うのが慣用的です。&str にしておくと String&str も両方渡せます。

// &str を引数にすると String も &str も渡せる(推奨)
fn greet(name: &str) {
    println!("こんにちは、{}さん", name);
}

let name_owned = String::from("田中");
let name_borrowed: &str = "鈴木";

greet(&name_owned);   // String を渡す(& で借用)
greet(name_borrowed); // &str をそのまま渡す
// String を引数にすると &str を渡せない
fn greet(name: String) {
    println!("こんにちは、{}さん", name);
}

greet(String::from("田中"));  // OK
greet("鈴木");                 // コンパイルエラー

文字列を所有したい(関数内でヒープに保存するなど)場合は String を引数にします。


よく使うメソッド

共通(String・&str 両方で使える)

let s = "hello world";

s.len()                    // バイト数: 11
s.is_empty()               // 空かどうか: false
s.contains("world")        // 含むか: true
s.starts_with("hello")     // 先頭: true
s.ends_with("world")       // 末尾: true
s.to_uppercase()           // "HELLO WORLD"
s.to_lowercase()           // "hello world"
s.trim()                   // 前後の空白・改行を除去
s.replace("world", "Rust") // "hello Rust"
s.split(" ")               // イテレータで分割

String 専用(変更系)

let mut s = String::from("hello");

s.push('!');           // 文字を追加
s.push_str(" world"); // 文字列を追加
s.clear();            // 内容を消去
s.insert(0, 'H');     // 位置を指定して文字を挿入
s.truncate(3);        // 長さを3バイトに切り詰める

+ 演算子と format! での結合

let s1 = String::from("hello");
let s2 = String::from(" world");

// + 演算子(s1 の所有権が移動する)
let s3 = s1 + &s2;   // s1 はここで使えなくなる

// format! マクロ(所有権の移動なし・複数結合に便利)
let s4 = String::from("hello");
let s5 = String::from(" world");
let s6 = format!("{}{}", s4, s5);  // s4・s5 はまだ使える

複数の文字列を結合する場合は format! が読みやすくおすすめです。


まとめ

状況 使う型
文字列リテラルを参照するだけ &str
文字列を変更・追加したい String
関数で文字列を受け取るだけ &str(String も渡せる)
関数で文字列を所有したい String
&strString に変換 .to_string() / String::from()
String&str に変換 &s / .as_str()

基本方針は「変更しないなら &str、変更・所有するなら String」です。迷ったときはまず &str で書いてみて、コンパイラに怒られたら String に変える、という進め方が実践的です。

Rustのトレイトについては「Rustのトレイト入門:定義・実装・ジェネリクスとの組み合わせ方」を参照してください。

RustのVec初期化については「RustのVec初期化まとめ:vec!マクロからVec::newまで」を参照してください。

RustのHashMapの使い方は「RustのHashMap入門:基本操作から頻出パターンまとめ」を参照してください。

Rustのファイル読み書きは「Rustのファイル読み書き入門:fs・BufReader・BufWriterの使い方」を参照してください。