<Rust 備忘録> 所有権と参照・借用
Rust のキー概念である所有権と参照・借用についてのメモ.
(メモリのモデル図とかはそのうち作れたらいいなぁ…)
所有権 (ownership) と移動 (move)
基本的なこと
String
型 やVec<T>
型等の参照型では,ポインタ・容量・長さを stack で,実際の値を heap で確保します.- この heap で確保された値を所有 (own) しているという意味で,所有権 (ownership) という概念があります.
- なお,数値型や
char
型等の基本型は,値を stack で確保します.
所有権
String
型で見てみます.
次のように変数宣言した段階で,heap 上に確保された値の所有権は, a
にあります.
let a = "hello".to_string();
所有権の移動
b
に a
の値を代入すると,値の所有権は a
から b
へ移動 (move) します.
let a = "hello".to_string(); let b = a; assert_eq!("hello", b);
ここで,変数 a
を使おうとすると,次のようにコンパイルエラーになります.
let a = "hello".to_string(); let b = a; assert_eq!("hello", a);
error[E0382]: use of moved value: `a` --> src/main.rs:7:23 | 6 | let b = a; | - value moved here 7 | assert_eq!("hello", a); | ^ value used here after move | = note: move occurs because `a` has type `std::string::String`, which does not implement the `Copy` trait
Clone
Rust ではデフォルトで上のように「値の所有権の移動」が行われますが,例外的にメモリ上に「値をコピー(クローン)」することもできます.
次のように書くことで, a
の値とは別の領域に値のコピー(すなわち b
の値)が作られます.
let a: String = "hello".to_string(); let b = a.clone(); assert_eq!("hello", a); assert_eq!("hello", b);
この .clone()
メソッドにより,多くの所有権関連のコンパイルエラーは回避できます.しかし,闇雲に使うと無駄にメモリを消費していくので,次節にて述べる参照を上手く用いながら,できるだけ Clone しないような実装ができればベターかもしれません.
Copy Trait
Copy
Trait を実装している型では,デフォルトで値はコピーされます.
例えば, i32
型は Copy
Trait を実装しており,次のような記述をしてもエラーになりません.
let a: i32 = 0; let b = a; assert_eq!(0, b);
参照 (reference) と借用 (borrowing)
- 参照は,所有権を持たないポインタ型です.
- 値に対する参照を作ることを,借用といいます.
- 参照には,デフォルトの初期値(
NULL
のような)は存在しません. - 「参照の参照」みたいなこともできます.
2種類の参照(借用)
種類 | 共有参照 | 可変参照 |
---|---|---|
借用 | immutable | mutable |
読み出し | ◯ | ◯ |
変更 | × | ◯ |
複数参照 | ◯ | × |
記法 | &a |
&mut a |
- 共有参照(immutable な借用)の例
let a = 0; let b: &i32 = &a; // 借用! assert_eq!(0, *b);
ここでは *b
で明示的に参照解決(つまりポインタ型から参照先の値を取り出す)しています.
immutable な借用は,何回でも可能です.
let a = 0; let b: &i32 = &a; let c: &i32 = &a; assert_eq!(0, *b); assert_eq!(0, *c);
- 可変参照(mutable な借用)の例
let mut a = 0; // 参照元の a も mutable である必要がある { let mut b: &mut i32 = &mut a; // 借用! *b += 1; // 参照解決して値を変更 assert_eq!(1, *b); } // b がスコープから外れる assert_eq!(1, a); // a の値も変更されていることがわかる
mutable な借用は,原則1回のみ許されます.次の2つのコードは通りません.
let mut a = 0; let mut b: &mut i32 = &mut a; let mut c: &mut i32 = &mut a; // 2回目の借用をしようとすると怒られる
let mut a = 0; let mut b: &mut i32 = &mut a; let c : &i32 = &a; // immutable な借用と併用できない
ただし,次のコードでは, b
が解法された後に c
で借用をしているため,コンパイルエラーになりません.
let mut a = 0; { let mut b: &mut i32 = &mut a; assert_eq!(0, *b); } let mut c: &mut i32 = &mut a; assert_eq!(0, *c);
参照の代入
let a = 0; let b = 1; let mut c = &a; assert_eq!(0, *c); c = &b; // 参照 c へ参照を再代入 assert_eq!(1, *c); assert_eq!(0, a); // 「借用しなおす」ため, a の値は変更されない
ライフタイム (lifetime)
- ライフタイム:参照を(安全に)利用できる範囲のこと
次のコードでは, assert_eq!(*a, 1)
の段階で既に b
がスコープから外れています.
これを放っておくとダングリングポインタが生じてしまいますが, Rust の場合はコンパイラがこれを阻止します.
let mut a: &i32 = &0; // a を `&i32` 型として定義 { let b = 0; a = &b; // ここで a の値を変更する } assert_eq!(*a, 0); // a は b への参照を持つが, b がスコープから外れているためエラー
↓ 実行結果.
error[E0597]: `b` does not live long enough --> src/main.rs:10:10 | 10 | a = &b; | ^ borrowed value does not live long enough 11 | } | - `b` dropped here while still borrowed 12 | assert_eq!(*a, 0); 13 | } | - borrowed value needs to live until here
ただし,次のコードは通ります.
let mut a: &i32 = &0; { let b = 0; let a = &b; // ここで a を新たに宣言している } // ↑この新たな宣言ごとここで吹っ飛ぶ assert_eq!(*a, 0); // ここでは,1行目で宣言した a の値が生きている