Rustを学ぶ上で避けて通れないのが「所有権(Ownership)」と「借用(Borrowing)」という概念です。Rustのメモリ管理は他の言語と比べてユニークであり、これらの仕組みを理解することで、Rustが提供する高いメモリ安全性を体感できるようになります。
この記事では、Rustの所有権と借用の基本的なルールについて、実際のコード例を用いながら解説します。
所有権とは?
Rustでは、すべての値は必ず「所有者(owner)」を持ちます。
所有者は1つしか存在せず、ある変数が値を所有している場合、他の変数にその所有権を譲渡したり、借用したりしなければなりません。
この所有権システムにより、Rustはメモリの解放を自動的に管理します。
所有権のルール
- 各値には所有者が一つ存在する。
- 所有者がスコープを抜けると、値はドロップされメモリが解放される。
- 所有権は移動することができる。
以下のコードで、所有権の基本的な動きを確認してみましょう。
fn main() {
let s1 = String::from("こんにちは"); // s1がString型の値を所有
let s2 = s1; // 所有権がs1からs2に移動(ムーブ)
// println!("{}", s1); // s1の所有権はすでに移動しているため、ここでエラー
println!("{}", s2); // s2は正しく所有しているので表示可能
}
ポイント:
let s2 = s1
の行で、s1
からs2
に所有権がムーブ(移動)されます。所有権が移動した後、s1
は無効になり、もう使用できません。println!
マクロでs1
を使おうとするとコンパイルエラーが発生します。
所有権のムーブとコピー
基本的に、所有権はムーブされますが、固定サイズのデータ型(整数や浮動小数点など)はムーブではなく「コピー」されます。
fn main() {
let x = 5; // xはi32型の整数(固定サイズの型)
let y = x; // xの値がコピーされ、yも所有
println!("x: {}, y: {}", x, y); // xとy両方が使用可能
}
ポイント:
i32
のような固定サイズの型は、コピーされるため、所有権が移動するわけではありません。x
もy
も有効な状態で使用できます。
借用とは?
所有権が移動せずに、変数を使い回す方法が「借用(Borrowing)」です。
Rustでは、変数を借用することで、所有権を保ったまま他の部分でその値を参照することができます。
参照と借用
Rustでは、&
を使って参照を借用できます。
借用には「不変借用」と「可変借用」の2種類があります。
fn main() {
let s1 = String::from("Rust");
let len = calculate_length(&s1); // s1を借用(不変借用)
println!("'{}'の長さは{}です。", s1, len); // s1は引き続き有効
}
fn calculate_length(s: &String) -> usize {
s.len() // sの長さを返す
}
ポイント:
&s1
でs1
を借用していますが、所有権は移動していません。- 参照(借用)された値は読み取り専用となります。
可変借用
一度に1つだけ、可変な借用が可能です。
可変借用をするには、&mut
を使用します。
fn main() {
let mut s = String::from("Hello"); // mutで可変なStringを宣言
change(&mut s); // 可変借用
println!("{}", s); // 変更後のsを表示
}
fn change(s: &mut String) {
s.push_str(", world!"); // 文字列に追加
}
ポイント:
- 可変借用は、同じ変数に対して同時に複数存在することはできません(競合を防ぐため)。
&mut s
で可変な借用を行い、関数内でpush_str
メソッドで値を変更します。
借用規則
Rustでは、安全なメモリ管理を実現するためにいくつかのルールがあります。
不変借用と可変借用のルール
- 不変借用は何回でも可能です。
- 可変借用は同時に1つだけです。
- 不変借用と可変借用は同時には許されません。
fn main() {
let mut s = String::from("Rust");
let r1 = &s; // 不変借用
let r2 = &s; // 不変借用(何回でもOK)
// let r3 = &mut s; // 可変借用を同時に行うとエラー
println!("{}と{}", r1, r2); // 不変借用の値は安全に使用可能
}
ポイント:
- 不変借用は複数同時に行うことができますが、可変借用と同時に行うとエラーになります。
スライス
スライスは、コレクション全体ではなく、その一部を借用したい場合に使います。
fn main() {
let s = String::from("hello world");
let hello = &s[0..5]; // スライスを作成
let world = &s[6..11];
println!("{} {}", hello, world);
}
ポイント:
- スライスは不変借用であり、スライス中の要素を変更することはできません。
実践例
借用と所有権を使った文字列の操作
fn main() {
let mut s = String::from("Rust Programming");
// 文字列を分割して取得する関数を呼び出す
let word = first_word(&s);
println!("最初の単語: {}", word);
s.clear(); // 可変借用で文字列を空にする
// println!("最初の単語: {}", word); // この部分はエラーになる
}
fn first_word(s: &String) -> &str {
let bytes = s.as_bytes();
for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return &s[0..i];
}
}
&s[..]
}
ポイント:
- 借用とスライスを組み合わせて、効率的に文字列の一部を扱うことができます。
s.clear()
で文字列を変更した場合、以前のスライス参照は無効になります。
練習問題
練習問題1: 所有権の移動
以下のコードを修正し、所有権が適切に管理されるようにしてください。
fn main() {
let s1 = String::from("Rust");
let s2 = s1;
println!("{}", s1); // この行でエラーが発生する
}
練習問題2: 借用を使った文字列操作
文字列を入力し、その文字列が含む単語の数を返す関数を作成してください。関数は借用を使用し、所有権を移動しないようにしてください。
まとめ
今回の記事では、Rustの所有権と借用に関する基本的な概念を学びました。
これらのメモリ管理機構により、Rustは高い安全性を持ちながら効率的に動作する言語となっています。
所有権の移動や借用のルールに慣れることで、より深いRustプログラミングの理解を深めることができるでしょう。