所有権 / 移動(move)
| 対応: | Rust 1.0(2015) |
|---|
Rustの所有権(ownership)は、各値に必ず1つの所有者があるという独自のルールで、メモリの安全性をコンパイル時に保証します。所有者がスコープを抜けると、値は自動的に解放されます。
構文
// 所有権の移動(move)
let 変数1 = 値;
let 変数2 = 変数1; // 変数1の所有権が変数2に移動します(変数1は無効)。
// 関数への所有権移動
fn 関数名(引数: 型) { ... }
関数名(変数); // 変数の所有権が関数に移動します。
// 明示的なメモリ解放
drop(変数);
概要一覧
| ルール | 概要 |
|---|---|
| 所有者は1つ | 各値に対して所有者となる変数は常に1つだけです。 |
| スコープで解放 | 所有者がスコープを抜けると、値のメモリが自動的に解放されます。 |
| moveセマンティクス | 代入・関数渡しで所有権が移動し、元の変数は使用不可になります。 |
| drop() | 所有権を持つ変数を即座に解放したい場合に使用します。 |
サンプルコード
所有権の移動(ムーブ)と、コピーセマンティクスの動作を確認するサンプルコードです。
sample_ownership_move.rs
fn take_ownership(s: String) {
println!("{}", s);
} // ここでsが解放されます。
fn main() {
let s1 = String::from("hello");
let s2 = s1; // s1の所有権がs2に移動します。
// println!("{}", s1); // エラー:s1はもう使えません。
println!("{}", s2); // 『hello』と出力されます。
let s3 = String::from("world");
take_ownership(s3); // s3の所有権が関数に移動します。
// println!("{}", s3); // エラー:s3はもう使えません。
// drop()で即座に解放します。
let s4 = String::from("drop me");
drop(s4);
// println!("{}", s4); // エラー:s4は解放済みです。
// Copyトレイトを持つ型はmoveではなくコピーされます。
let x = 5;
let y = x; // xはまだ使えます。
println!("x={}, y={}", x, y); // 両方使えます。
}
コンパイルして実行すると次のように出力されます。
rustc ownership_move.rs ./ownership_move hello world x=5, y=5
応用パターン
sample_ownership_advanced.rs
// パターン1: 関数から所有権を返す
fn create_name() -> String {
String::from("Kiryu Kazuma") // 所有権を呼び出し元に返す
}
fn process(name: String) -> String {
// 引数の所有権を受け取り、処理後に所有権を返す
format!("名前: {}", name)
}
// パターン2: Copy トレイト(プリミティブ型は自動的にコピー)
fn show_i32(n: i32) {
println!("{}", n);
}
// パターン3: Clone で明示的にコピー
fn show_string(s: &String) {
println!("{}", s);
}
// パターン4: move クロージャ(スレッドにデータを渡す場合)
use std::thread;
fn main() {
// 所有権の返却
let name = create_name();
let result = process(name); // name の所有権は process() に移動
println!("{}", result);
// name は使用不可(process() に移動した)
// Copy: i32 は Copy トレイトを実装しているため移動しない
let x = 42i32;
show_i32(x); // x のコピーが渡される
println!("x はまだ使える: {}", x); // OK
// Clone: String は Clone で明示的にコピーする
let s = String::from("Majima Goro");
let s_clone = s.clone(); // s のコピーを作る
show_string(&s);
show_string(&s_clone);
println!("どちらも使える: {} / {}", s, s_clone);
// move クロージャ: クロージャが変数の所有権を取得する
let message = String::from("こんにちは");
let handle = thread::spawn(move || {
// message の所有権はこのクロージャに移動した
println!("スレッドから: {}", message);
});
// println!("{}", message); // コンパイルエラー: move されている
handle.join().unwrap();
}
rustc sample_ownership_advanced.rs ./sample_ownership_advanced 名前: Kiryu Kazuma x はまだ使える: 42 Majima Goro Majima Goro どちらも使える: Majima Goro / Majima Goro スレッドから: こんにちは
よくあるミス
よくあるミス1: 所有権移動後に変数を使おうとするコンパイルエラー
関数に値を渡すか別の変数に代入すると所有権が移動します。移動後は元の変数は使用できません。
fn take_ownership(s: String) {
println!("{}", s);
}
fn main() {
let s = String::from("Saejima");
take_ownership(s); // s の所有権が移動する
// コンパイルエラー: s の所有権は take_ownership() に移動した
// println!("{}", s);
}
fn take_ownership(s: String) {
println!("{}", s);
}
fn main() {
let s = String::from("Saejima");
// 解決策1: 参照を渡す(所有権を移動しない)
println!("{}", &s); // s はまだ使える
take_ownership(s.clone()); // clone で複製してから渡す
println!("{}", s); // s はまだ使える
// 解決策2: 関数が所有権を返す設計にする
}
よくあるミス2: Copy と Clone の混同
Copy トレイトはスタック上のデータに暗黙的なコピーを行います(i32, bool, f64 など)。Clone はヒープのデータを含む型(String, Vec など)の明示的なコピーです。
fn main() {
// i32 は Copy: 自動でコピーされる
let x: i32 = 10;
let y = x; // コピー(move ではない)
println!("{} {}", x, y); // 両方使える
// String は Copy ではない: move になる
let s1 = String::from("hello");
let s2 = s1; // move(コピーではない)
// println!("{}", s1); // コンパイルエラー: s1 は move された
// Clone で明示的にコピーする
let s3 = String::from("world");
let s4 = s3.clone(); // ヒープデータも含めてコピー
println!("{} {}", s3, s4); // 両方使える
}
概要
Rustの所有権システムはガベージコレクタなしにメモリの安全性を保証します。値がスコープを抜けると自動的に『drop()』が呼ばれ、メモリが解放されます(RAIIパターン)。RAII(Resource Acquisition Is Initialization)とは、リソースの確保と解放を変数のライフサイクルに紐づけるパターンです。
所有権が移動した変数を使用しようとすると、コンパイルエラーになります。値を複数の場所で使いたい場合は、借用(&参照)またはclone()を使用してください。
整数・浮動小数点・bool・char などCopyトレイトを実装した型は、代入時にコピーが行われるため元の変数も引き続き使用できます。
記事の間違いや著作権の侵害等ございましたらお手数ですがこちらまでご連絡頂ければ幸いです。