📝

String vs &str — Rubyにはない文字列の二つの顔

所有する文字列と借用する文字列の違い

Rubyでは文字列はシンプル。String1つ。作って、変えて、渡す。Rustでは文字列が2種類。

String — 所有する文字列

let s = String::from("hello");
let s = "hello".to_string();  // 同じ結果

ヒープに確保される。サイズが変わる。Rubyの通常のStringに最も近い。

&str — 借用する文字列

let s: &str = "hello";  // 文字列リテラルは常に&str

どこかにある文字列データへの参照(スライス)。サイズ変更不可。Rubyのfrozen stringに近い。

いつどちらを使うか

関数の引数 → &str

// ✅ 良い — Stringも&strも受けられる
fn greet(name: &str) {
    println!("Hello, {}!", name);
}
greet("world");           // &str渡し
greet(&my_string);        // String → &str自動変換

// ❌ 不必要に制限的
fn greet(name: String) {  // Stringのみ受付
    println!("Hello, {}!", name);
}

構造体のフィールド → String

struct User {
    name: String,  // 所有する必要があるのでString
}

変換

// &str → String
let owned: String = "hello".to_string();
let owned: String = String::from("hello");
let owned: String = "hello".to_owned();

// String → &str
let borrowed: &str = &owned;
let borrowed: &str = owned.as_str();

文字列連結 — Rubyより面倒

Rubyの"Hello, " + name"Hello, #{name}"はシンプル。Rustは少し違う。

// format! — Rubyの"#{}"/sprintfに相当
let msg = format!("Hello, {}!", name);

// +演算子 — 左辺がStringでなければならない
let msg = String::from("Hello, ") + name;

// push_str — Stringに&strを追加
let mut msg = String::from("Hello, ");
msg.push_str(name);

大抵format!を使えばいい。Rubyの文字列補間に最も近い。

なぜ2種類あるか

性能と所有権。&strはコピーなしで既存の文字列データを指す。関数に文字列を渡す時、ヒープ割り当てなしで参照だけ渡せる。これがRustが速い理由の一つ。

キーポイント

1

String = ヒープ確保、可変 / &str = 参照、不変

2

関数の引数は&strで受けるのが慣例 — Stringも自動変換される

3

構造体のフィールドはString — 所有権が必要だから

4

format!()がRubyの文字列補間(#{})に最も近い

メリット

  • 不要な文字列コピーを&strで回避 — 性能のメリット
  • Stringと&strの区別が所有権理解の良い入口

デメリット

  • \"hello\".to_string()のような変換が面倒
  • 構造体に&strを入れるにはライフタイム注釈が必要 — 初心者キラー

ユースケース

APIレスポンスパース — JSONから取り出した文字列の処理 設定ファイル読み込み — 内容はString、パース中は&str

参考資料