Rust lang

Rustの所有権と借用:初心者から中級者への完全ガイド

Rustは、メモリ管理を手動で行う必要がなく、安全かつ高速にプログラムを実行できる言語です。その核心となるのが「所有権(Ownership)」と「借用(Borrowing)」の概念です。本記事では、初心者から中級者向けに、所有権と借用の仕組みを詳しく解説し、具体的なコード例を交えて理解を深めていきます。

1. 所有権とは?

Rustの所有権システムの基本ルールは以下の3つです:

  1. 各値は一つの所有者(Owner)を持つ。
  2. 所有者がスコープを抜けると、その値は自動的に解放される。
  3. 値の所有権は移動(Move)できるが、複数の所有者は持てない。

これにより、Rustはガベージコレクタを使わずにメモリの管理を保証します。

1.1 所有権の移動(Move)

所有権は変数の代入時に移動します。例えば、以下のコードではString型の所有権がs1からs2へ移動します。

fn main() {
    let s1 = String::from("hello");
    let s2 = s1;
    println!("{}", s1); // コンパイルエラー
}

s1の所有権はs2に移動しているため、s1は無効になります。この制約により、二重解放エラーを防ぐことができます。

1.2 クローン(Clone)によるコピー

値をコピーしたい場合は、cloneメソッドを使用します。

fn main() {
    let s1 = String::from("hello");
    let s2 = s1.clone();
    println!("{}", s1); // 問題なし
}

clone()を使うと、新しいメモリ領域にデータがコピーされ、両方の変数が有効な状態を維持できます。

1.3 スタックに置かれる型のコピー(Copyトレイト)

i32のようなスタックに保存される型は、所有権の移動ではなくコピーが行われます。

fn main() {
    let x = 5;
    let y = x; // xはそのまま有効
    println!("{}", x);
}

これは、整数などの基本型がCopyトレイトを持っているためです。

1.4 所有権と関数

所有権は関数に渡す際にも移動します。

fn takes_ownership(s: String) {
    println!("{}", s);
} // ここでsの所有権が解放される

fn main() {
    let s = String::from("hello");
    takes_ownership(s);
    println!("{}", s); // エラー!
}

関数に渡した時点でStringの所有権は移動し、sは無効になります。

2. 借用(Borrowing)とは?

所有権を完全に移動せず、一時的に変数を参照する仕組みを「借用」と呼びます。Rustには以下の2種類の借用があります。

  1. 不変の借用(Immutable Borrow)
  2. 可変の借用(Mutable Borrow)

2.1 不変の借用

不変の借用を行うと、値を読み取ることはできますが、変更はできません。

fn print_length(s: &String) {
    println!("文字列の長さ: {}", s.len());
}

fn main() {
    let s = String::from("hello");
    print_length(&s);
    println!("{}", s); // 問題なし
}

不変の借用は複数同時に存在可能です。

2.2 可変の借用

可変の借用を使うと、値を変更できますが、一度に1つの可変参照しか持てません。

fn change(s: &mut String) {
    s.push_str(" world");
}

fn main() {
    let mut s = String::from("hello");
    change(&mut s);
    println!("{}", s); // hello world
}

このルールにより、データ競合が防がれます。

3. ライフタイムと借用

借用のもう一つの重要な概念が「ライフタイム(lifetime)」です。ライフタイムを指定することで、借用が適切な範囲で有効になるようにコンパイラがチェックします。

fn longest<'a>(s1: &'a str, s2: &'a str) -> &'a str {
    if s1.len() > s2.len() {
        s1
    } else {
        s2
    }
}

fn main() {
    let s1 = "long string";
    let s2 = "short";
    let result = longest(s1, s2);
    println!("{}", result);
}

ライフタイムを明示することで、安全な参照を維持できます。

4. 所有権と借用のまとめ

  • 所有権は一つの変数のみが持ち、スコープを抜けると解放される。
  • 値の移動(Move)によって、所有権は別の変数に移る。
  • clone()を使うと、データをコピーして所有権を保持できる。
  • 不変の借用は複数可能だが、可変の借用は一度に1つのみ。
  • 可変の借用と不変の借用は同時に使用できない。
  • スライスを使うと、データの一部を借用できる。
  • ライフタイムを利用すると、借用の有効範囲を明示できる。