Rustの所有権と借用の仕組みをわかりやすく解説

先生

Rustの所有権と借用をマスターして、メモリ安全なコードを書こう!

Rustの所有権とは?メモリ管理の基本

Rustは、他の多くの言語とは異なり、ガベージコレクションを持ちません。代わりに、所有権というシステムを使ってメモリ安全性を実現しています。所有権は、コンパイル時にメモリ関連のエラーを防ぐための強力な仕組みです。

所有権のルールは以下の3つです。

1. 各値は、所有者と呼ばれる変数と関連付けられています。

2. ある時点で、所有者はただ1つです。

3. 所有者がスコープから外れると、値は破棄されます(メモリが解放されます)。

これらのルールを理解することで、Rustで安全かつ効率的なコードを書くことができます。

所有権の移動(ムーブ)

Rustでは、所有権は変数から別の変数へ「移動(ムーブ)」できます。これは、値をコピーするのではなく、所有権だけが移動することを意味します。


fn main() {
    let s1 = String::from("hello");
    let s2 = s1; // s1からs2へ所有権が移動
    // println!("{}", s1); // エラー!s1はもう有効ではない
    println!("{}", s2); // これはOK
}

上記の例では、s1からs2へ所有権が移動したため、s1はもはや有効ではありません。これは、二重解放を防ぐためのRustの設計によるものです。

クローン(複製)によるコピー

所有権を移動させずに、値をコピーしたい場合は、cloneメソッドを使用します。cloneは、ヒープ上のデータをコピーするため、所有権の移動よりもコストがかかります。


fn main() {
    let s1 = String::from("hello");
    let s2 = s1.clone(); // s1の値をコピー
    println!("s1 = {}, s2 = {}", s1, s2); // 両方とも有効
}

cloneを使用すると、s1s2は独立した値を持ちます。所有権は移動せず、それぞれの変数がそれぞれの値の所有者となります。

借用(Borrowing)とは?参照の仕組み

所有権を移動させずに、値にアクセスしたい場合は、借用(Borrowing)を使用します。借用は、値への参照を作成することを意味します。

Rustには、不変な参照(&)と可変な参照(&mut)の2種類があります。

不変な参照は、複数の場所から同時に値を読み取ることができます。可変な参照は、排他的なアクセスを必要とし、同時に複数の可変な参照は存在できません。


fn main() {
    let s1 = String::from("hello");
    let len = calculate_length(&s1); // s1への不変な参照を渡す
    println!("The length of '{}' is {}.", s1, len);
}

fn calculate_length(s: &String) -> usize {
    s.len()
}

上記の例では、calculate_length関数はs1への不変な参照を受け取ります。関数内でs1の値を使用できますが、変更することはできません。s1の所有権は移動していません。

可変な参照(Mutable References)

値を変更する必要がある場合は、可変な参照を使用します。ただし、可変な参照には、同時に複数の可変な参照が存在できないという制限があります。


fn main() {
    let mut s = String::from("hello");
    change(&mut s); // sへの可変な参照を渡す
    println!("{}", s);
}

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

上記の例では、change関数はsへの可変な参照を受け取ります。関数内でsの値を変更することができます。sの所有権は移動していません。

Rustのコンパイラは、借用規則を厳格にチェックし、データ競合を防ぎます。

ダングリング参照(Dangling References)

ダングリング参照とは、すでに解放されたメモリを参照する参照のことです。Rustのコンパイラは、ダングリング参照が発生する可能性のあるコードをコンパイル時に検出します。


// コンパイルエラーになる例
// fn dangle() -> &String { // dangleはStringへの参照を返す
//     let s = String::from("hello"); // sはdangle内で生成される
//     &s // String sへの参照を返す
// } // ここでsはスコープを抜け、ドロップされる。メモリは解放される。 
//
// fn main() {
//     let reference_to_nothing = dangle(); // reference_to_nothingはダングリング参照
// }

上記の例では、dangle関数が終了すると、sはスコープから外れ、メモリが解放されます。そのため、dangleが返す参照はダングリング参照となります。Rustコンパイラは、このようなコードをコンパイルしません。

参考リンク

まとめ

Rustの所有権と借用は、メモリ安全性を実現するための重要な仕組みです。所有権の移動、クローン、借用(不変な参照と可変な参照)の概念を理解することで、Rustで安全かつ効率的なコードを書くことができます。コンパイラは借用規則を厳格にチェックし、ダングリング参照やデータ競合を防ぎます。これらの仕組みをマスターすることで、自信を持ってRustプログラミングに取り組むことができるでしょう。