あっぽログ
← 記事一覧に戻る

RustのStringと&strを使い分ける:文字列型の違いと変換方法を徹底解説

はじめに

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 バイナリ領域
生成コストやや高い低い
典型的な使い方動的に文字列を組み立てる読み取りのみの参照

変換方法を覚えよう

&strString

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初心者が最初に戸惑うポイントですが、「所有権があるかどうか」という観点で考えると自然に使い分けられるようになります。ぜひ手を動かして試してみてください!

← 記事一覧に戻る