Rustの所有権と借用を理解する
/ ARTICLE
Rustの所有権:メモリ安全性の鍵
Rustが他のプログラミング言語と異なる最大の特徴は、その**所有権システム(Ownership System)**です。このシステムは、ガベージコレクションを使わずに、コンパイル時にメモリ安全性を保証します。
所有権とは?
所有権は、Rustにおけるメモリ管理の基本的な概念です。以下の3つの主要ルールで構成されています。
ルール1: 値の所有権
各値は、ただ一つのオーナー(所有者)を持ちます。値が変数に代入されると、その変数がオーナーになります。
fn main() {
let s1 = String::from("hello");
let s2 = s1; // s1の所有権がs2に移動
}この例は**ムーブセマンティクス**を示しています。`s2 = s1`の時点で、所有権は`s1`から`s2`に移動し、`s1`は無効になります。
ルール2: スコープを出ると所有権も終わる
オーナーのスコープが終わると、その値は自動的にドロップ(解放)されます。
```rust
fn main() {
let s = String::from("world");
// sがスコープを出る時点で自動的にメモリが解放される
} // ここでsはドロップされる
```
ルール3: 関数呼び出しでの所有権移動
関数に値を渡すと、所有権が関数に移動します。
借用(Borrowing)の力
所有権システムだけでは、同じ値に複数からアクセスしたい場合に困ります。そこで登場するのが**借用**です。
不変借用(Immutable Borrow)
参照(`&`)を使って値を借りることができます。この場合、借り手は値を読むことはできますが、変更はできません。
```rust
fn main() {
let s = String::from("borrow me");
let len = calculate_length(&s); // 参照を借用
println!("'{}' の長さは {}", s, len);
// sは引き続き有効!所有権は保持したまま
}
fn calculate_length(s: &String) -> usize {
s.len()
} // sはスコープを出るが、所有権を持たないので何も起きない
可変借用(Mutable Borrow)
ときには借り手が値を変更する必要があります。これを可能にするのが**可変借用**です。
```rust
fn main() {
let mut s = String::from("hello");
change_string(&mut s); // 可変参照を借用
println!("{}", s);
}
fn change_string(s: &mut String) {
s.push_str(" world");
}
```
### 借用ルール:制限と自由のバランス
Rustの借用には重要な制限があり、これがメモリ安全性を保証します:
1. 不変借用と可変借用は混在できない
任意の時点で、以下のいずれかが成立する必要があります:
- 複数の不変借用(`&T`)
- たった1つの可変借用(`&mut T`)
```rust
fn main() {
let mut s = String::from("hello");
let r1 = &s; // OK - 不変借用
let r2 = &s; // OK - 不変借用
let r3 = &mut s; // ❌ エラー!可変借用は不変借用と混在できない
println!("{}, {}, {}", r1, r2, r3);
}
```
2. 借用のスコープ(NLL - Non-Lexical Lifetimes)
Rust 2018から、借用のスコープは、借用がその後で使用されるまでです。
```rust
fn main() {
let mut s = String::from("hello");
let r1 = &s;
let r2 = &s;
println!("{}, {}", r1, r2); // r1とr2の最後の使用
let r3 = &mut s; // OK!r1とr2のスコープは終わっているから
println!("{}", r3);
}
```
### ライフタイム(Lifetime)の導入
より複雑な場合、参照がどの程度の期間有効であるかを明示的に指定する必要があります。これが**ライフタイム**です。
```rust
// ライフタイム 'a を宣言
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
fn main() {
let s1 = String::from("long string");
let s2 = String::from("short");
let result = longest(s1.as_str(), s2.as_str());
println!("最も長い文字列: {}", result);
}
```
### 実践的な例
#### 例1: ベクタの操作
```rust
fn main() {
let mut nums = vec![1, 2, 3, 4, 5];
double(&mut nums); // 可変借用
print_nums(&nums); // 不変借用
}
fn double(nums: &mut Vec<i32>) {
for num in nums.iter_mut() {
num = 2; // * は逆参照演算子
}
}
fn print_nums(nums: &Vec<i32>) {
for num in nums {
print!("{} ", num);
}
println!();
}
```
#### 例2: 構造体と所有権
```rust
struct User {
name: String,
email: String,
}
fn main() {
let user = User {
name: String::from("Alice"),
email: String::from("alice@example.com"),
};
print_user_info(&user); // 借用
print_user_info(&user); // OK - 複数借用可能
println!("User: {}", user.name); // userはまだ有効
}
fn print_user_info(user: &User) {
println!("名前: {}, メール: {}", user.name, user.email);
}
```
### よくある落とし穴
#### 落とし穴1: ダングリング参照
Rustはコンパイル時にこれを防ぎます:
```rust
fn dangle() -> &String { // ❌ エラー
let s = String::from("hello");
&s // sはスコープを出るのに参照を返そうとしている
}
```
正しい方法:
```rust
fn no_dangle() -> String {
let s = String::from("hello");
s // 所有権を移動させて返す
}
```
#### 落とし穴2: クローン vs 借用
```rust
fn main() {
let s1 = String::from("hello");
// 方法1: クローン(コスト高)
let s2 = s1.clone();
println!("{}, {}", s1, s2); // 両方使える
// 方法2: 借用(推奨)
let s3 = String::from("world");
use_string(&s3);
println!("{}", s3); // s3はまだ有効
}
fn use_string(s: &String) {
println!("{}", s);
}
```
### 所有権システムのメリット
1. メモリ安全性: ダングリング参照、ダブルフリー、バッファオーバーフロー等を防止
2. パフォーマンス: ガベージコレクションの必要がなく、予測可能な実行時間
3. スレッド安全性: 所有権とムーブセマンティクスにより、安全な並行処理
4. 明示的なメモリ管理: Cのようなメモリリークの可能性を排除
### まとめ
Rustの所有権と借用システムは、最初は複雑に見えるかもしれません。しかし、これらのルールを理解することで:
- メモリ安全なコードを書くことができます
- ランタイムエラーを減らせます
- パフォーマンスを維持できます
Rustの成長とともに、所有権とのより深い関係が築かれるでしょう。"Move semantics" について深く理解することで、より効率的で安全なRustコードが書けるようになります。