Rustの所有権・参照・関数のについて

Rustの所有権システム、参照(ポインタ)、関数への渡し方について、String型と&str型の両方を含めて整理します。

1. 2つの文字列型

String型(ヒープ、所有権あり)

let s1 = String::from("Hello");
  • ヒープに確保
  • 所有権を持つ
  • 可変にできる
  • サイズが動的

&str型(静的領域、参照)

let s2 = "Hello";
  • プログラムバイナリに埋め込まれる
  • 参照(借用)
  • 不変
  • サイズが固定

2. 関数への渡し方(完全版)

パターン1: Stringを値渡し(ムーブ)

fn main() {
    let s1 = String::from("Hello");
    take_string(s1);                // 所有権がムーブ
    // println!("{}", s1);          // ❌ エラー
}

fn take_string(s: String) {
    println!("{}", s);
}  // メモリ解放

パターン2: Stringの参照を渡す

fn main() {
    let s1 = String::from("Hello");
    borrow_string(&s1);             // 参照を渡す
    println!("{}", s1);             // ✓ OK
}

fn borrow_string(s: &String) {
    println!("{}", s);
}

パターン3: &strを値渡し(コピー)

fn main() {
    let s2 = "Hello";
    take_str(s2);                   // コピー(参照のコピー)
    println!("{}", s2);             // ✓ OK
}

fn take_str(s: &str) {
    println!("{}", s);
}

パターン4: Stringを&strとして渡す(推奨)

fn main() {
    let s1 = String::from("Hello");
    print_str(&s1);                 // &String → &str(自動変換)
    println!("{}", s1);             // ✓ OK
}

fn print_str(s: &str) {
    println!("{}", s);
}

3. 完全な比較表

変数の型宣言メモリ所有権関数の引数呼び出し元の変数
StringString::from(“Hello”)ヒープありs: Stringfunc(s)❌ 使えない
StringString::from(“Hello”)ヒープありs: &Stringfunc(&s)✓ 使える
StringString::from(“Hello”)ヒープありs: &mut Stringfunc(&mut s)✓ 使える
&str“Hello”静的なしs: &strfunc(s)✓ 使える
StringString::from(“Hello”)ヒープありs: &strfunc(&s)✓ 使える

4. メモリ配置の比較

String型

let s1 = String::from("Hello");
スタック              ヒープ
┌──────────┐      ┌─────────────┐
│ s1       │─────→│ H e l l o   │
│ ptr/len  │      └─────────────┘
└──────────┘

&str型

let s2 = "Hello";
スタック              プログラムバイナリ(静的領域)
┌──────────┐      ┌─────────────┐
│ s2       │─────→│ H e l l o   │
│ ptr/len  │      └─────────────┘
└──────────┘

&String型

let s1 = String::from("Hello");
let r = &s1;
スタック              スタック              ヒープ
┌──────────┐      ┌──────────┐      ┌─────────────┐
│ r        │─────→│ s1       │─────→│ H e l l o   │
│ ptr      │      │ ptr/len  │      └─────────────┘
└──────────┘      └──────────┘

5. 関数の引数の推奨パターン

パターン1: &strを受け取る(最も汎用的・推奨)

fn print_text(s: &str) {
    println!("{}", s);
}

fn main() {
    let s1 = String::from("Hello");
    let s2 = "World";
    
    print_text(&s1);                // String → &str
    print_text(s2);                 // &str → &str
}

メリット: String&strも受け取れる

パターン2: &Stringを受け取る(限定的)

fn print_string(s: &String) {
    println!("{}", s);
}

fn main() {
    let s1 = String::from("Hello");
    let s2 = "World";
    
    print_string(&s1);              // ✓ OK
    // print_string(s2);            // ❌ エラー
    print_string(&s2.to_string());  // 変換が必要
}

デメリット: &strを直接受け取れない

パターン3: Stringを受け取る(消費する)

fn consume(s: String) {
    println!("{}", s);
}

fn main() {
    let s1 = String::from("Hello");
    let s2 = "World";
    
    consume(s1);                    // ✓ OK(ムーブ)
    consume(s2.to_string());        // 変換が必要
}

6. 実践的なガイドライン

関数の引数として推奨される型

用途推奨型理由
読み取りのみ&strStringも&strも受け取れる
変更する&mut String元の変数を変更できる
消費するString所有権を取得
返すString新しい値を返す

変数宣言の使い分け

用途推奨型宣言
固定文字列&strlet s = “Hello”;
動的文字列Stringlet s = String::from(“Hello”);
変更する文字列Stringlet mut s = String::from(“Hello”);

7. 複数の参照のルール

ルール1: 不変参照は複数OK

fn main() {
    let s = String::from("Hello");
    
    let r1 = &s;                    // ✓ OK
    let r2 = &s;                    // ✓ OK
    let r3 = &s;                    // ✓ OK
    
    println!("{}, {}, {}", r1, r2, r3);
}

ルール2: 可変参照は1つだけ

fn main() {
    let mut s = String::from("Hello");
    
    let r1 = &mut s;                // ✓ OK
    // let r2 = &mut s;             // ❌ エラー!
    
    println!("{}", r1);
}

ルール3: 不変と可変は同時に持てない

fn main() {
    let mut s = String::from("Hello");
    
    let r1 = &s;                    // ✓ OK(不変参照)
    let r2 = &s;                    // ✓ OK(不変参照)
    // let r3 = &mut s;             // ❌ エラー!
    
    println!("{}, {}", r1, r2);
}

8. 完全な実践例

fn main() {
    // String型
    let s1 = String::from("Hello");
    
    // &str型
    let s2 = "World";
    
    // パターン1: &strを受け取る(推奨)
    print_any(&s1);                 // String → &str
    print_any(s2);                  // &str → &str
    
    // パターン2: &Stringを受け取る
    print_string_ref(&s1);          // ✓ OK
    // print_string_ref(s2);        // ❌ エラー
    
    // パターン3: Stringを受け取る(消費)
    let s3 = String::from("Rust");
    consume_string(s3);
    // println!("{}", s3);          // ❌ エラー
    
    // パターン4: 可変参照
    let mut s4 = String::from("Hello");
    append_world(&mut s4);
    println!("{}", s4);             // "Hello World"
}

// 最も汎用的(推奨)
fn print_any(s: &str) {
    println!("{}", s);
}

// String専用
fn print_string_ref(s: &String) {
    println!("{}", s);
}

// 所有権を取得
fn consume_string(s: String) {
    println!("{}", s);
}

// 変更する
fn append_world(s: &mut String) {
    s.push_str(" World");
}

9. Copyトレイトを持つ型との違い

String(Copyではない)

fn main() {
    let s = String::from("Hello");
    func(s);                        // ムーブ
    // println!("{}", s);           // ❌ エラー
}

fn func(s: String) {
    println!("{}", s);
}

i32(Copyトレイト)

fn main() {
    let x = 42;
    func(x);                        // コピー
    println!("{}", x);              // ✓ OK!
}

fn func(n: i32) {
    println!("{}", n);
}

&str(Copyトレイト)

fn main() {
    let s = "Hello";
    func(s);                        // コピー
    println!("{}", s);              // ✓ OK!
}

fn func(s: &str) {
    println!("{}", s);
}

理由: &strは参照なので、ポインタと長さ(16バイト)だけをコピーする

10. まとめ図

┌─────────────────────────────────────────────────────┐
│              文字列型と関数への渡し方                  │
└─────────────────────────────────────────────────────┘

String型(所有権あり)
  let s1 = String::from("Hello");
  
  ├─ func(s1)           → 所有権ムーブ、元の変数❌
  ├─ func(&s1)          → 参照、元の変数✓
  ├─ func(&mut s1)      → 可変参照、元の変数✓(変更される)
  └─ func(&s1) (&str)   → &strとして渡す、元の変数✓

&str型(参照、所有権なし)
  let s2 = "Hello";
  
  └─ func(s2)           → コピー、元の変数✓

推奨パターン
  関数の引数: fn func(s: &str)
  理由: StringもStrも受け取れる

11. 重要なポイント

  1. 関数の引数には&strを使うのが最も汎用的
    String&strも受け取れるため
  2. &strはCopyトレイトを持つ
    関数に渡しても元の変数が使える
  3. StringはCopyトレイトを持たない
    関数に渡すと所有権がムーブする
  4. 参照を使えばメモリ効率が良い
    データのコピーが発生しない
  5. 不変参照は複数持てるが、可変参照は1つだけ
    データ競合を防ぐため

12. 結論

基本的には不変参照(&T)を使い、変更が必要なら可変参照(&mut T)、値を消費する場合のみ所有権を渡す。

文字列を扱う関数では、&strを引数にするのが最も推奨される。

No responses yet

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です