
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
を使用すると、s1
とs2
は独立した値を持ちます。所有権は移動せず、それぞれの変数がそれぞれの値の所有者となります。
借用(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プログラミングに取り組むことができるでしょう。