Rustの所有権と借用の概念について、おのれなりの理解をまとめてみました。ほぼ個人メモです。
##バージョン
$ rustc --version
rustc 1.4.0 (8ab8581f6 2015-10-27)
##所有権の移動
プリミティブ型の代入式は、所有権の移動が発生しない。
let a = 100;
let b = a;
println!("a = {}", a);
// => a = 100
所有権の移動が発生するケースとして、構造体を例として考えてみる。以下例では、a
からb
に所有権を移動した後にa
を参照したが、a
はすでに所有権を失っているため、コンパイルエラーが発生した。
struct Point {
x :i32,
y :i32,
}
fn main()
{
let a = Point{x:100, y:200};
let b = a;
println!("a.x = {}, a.y = {}", a.x, a.y);
// => error: use of moved value: `a.x` [E0382]
}
同様に、関数の引数として渡すことも所有権の移動が発生するため、その後の使用はコンパイルエラーとなる。
struct Point {
x :i32,
y :i32,
}
fn f(x: Point)
{
}
fn main()
{
let a = Point{x:100, y:200};
f(a);
print!("a.x = {}, a.y = {}", a.x, a.y);
// => error: use of moved value: `a.x` [E0382]
}
じゃあ、構造体のコピーができないじゃん!と思ったが、
let b = Point{..a};
と書けばよいみたい。
所有権の移動に関して許されるのは「Ownerが、貸与中でないオブジェクトの所有権を移動すること」だけである。Ownerが、貸与中オブジェクトの所有権を移動することはできないし、また、Borrowerが、借用中オブジェクトの所有権を勝手に移動することもできない。
struct Point {
x :i32,
y :i32,
}
fn main()
{
let a = Point{x:100, y:200};
{
let b = &a;
let c = a; // NG
// => error: cannot move out of `a` because it is borrowed
}
{
let b = &mut a;
let c = *b; // NG
// => error: cannot move out of borrowed content
}
let d = a; // OK
所有権の返却
所有権を移動した場合、借用とは異なり、新しいOwnerが死んでも所有権は以前のOwnerには戻ってこない。ただし、所有権を取り戻すことはできる。
以下のコードは、このままではコンパイルエラーとなる。式文(1)をコメントアウトし、所有権を戻す式文(2)を有効にすれば、コンパイルは通る。
struct Point {
x :i32,
y :i32,
}
fn f(x :Point) -> Point { x }
fn main()
{
let mut a = Point{x:100, y:200};
f(a); // (1)
// a = f(a); // (2)
a;
// => (式文(1)が有効の場合)error: use of moved value: `a`
}
##借用(immutableな場合)
immutableなオブジェクトであれば、複数の借用者に貸出できる。immutable借用中のimmutableな又貸しは許されるが、mutableな又貸しは許されない。
fn f(x: &Point)
{
}
fn main()
{
let a = Point{x:100, y:200};
// Owner:Borrowerが1:nの関係でもOK
let b = &a;
let c = &a;
f(&a);
// immutableな又貸しはOK
let d = &*b;
// mutableな又貸しは許されない
let d = &mut *b; // NG
//error: cannot borrow immutable borrowed content `*b` as mutable
}
リテラルからの借用も可能。リテラルにも、ストレージ上に領域が割り当てられていることが分かる。
let x = &100;
println!("{:p}:x = {:p} => {}", &x, x, *x);
// => 0xbfad9974:x = 0xb7705084 => 100
let x = &"hoge";
println!("{:p}:x = {:p} => {}", &x, x, *x);
// => 0xbfad98fc:x = 0xb7713748 => hoge
Ownerのlifetimeは、Borrowerのlifetimeを包含しなければならない。OwnerがBorrowerより先に死んではならない(mutableな場合も同様)。
let b = {
let a = 100;
&a // NG
// => error: `a` does not live long enough
};
##借用(mutableな場合)
オブジェクトをmutable貸与するには、Ownerをmutableとし、かつ、貸出をmutableとしなければならない(Borrower自体がmutableである必要は無い)。
let mut a = 100;
let b = &mut a;
*b = 200; // OK
Borrower自体がmutableであるべきなのは、Borrower自体の借用元Ownerを変更する場合である。
let a = 100;
let b = 200;
let mut c = &a;
println!("c = {}", *c); // => c = 100
c = &b;
println!("c = {}", *c); // => c = 200
mutableなオブジェクトについて、immutableな貸与をした場合、貸与中オブジェクトへの書き込みはOwnerであっても許されない。読み取りは可能。これは、immutableなオブジェクトの場合と同様の挙動となる。
let mut a = 100;
{
let x = &a;
a = 200; // write NG
// => error: cannot assign to `x` because it is borrowed
a; // read OK
}
a = 200; // write OK
mutableな又貸しは許される。Ownerが書き込みを許可して貸与した以上、書き込む主体が誰なのかはOwnerは気にしない。
let mut a = 100;
{
let b = &mut a;
{
let c = &mut *b; // OK
*c = 200; // OK
}
*b = 300; // OK
}
a = 400; // OK
mutableなオブジェクトについて、immutableな貸与をした先で又貸しをした場合、その挙動はimmutableなオブジェクトを又貸しした場合と同様の挙動となる。
つまり、オブジェクトのimmutable/mutableは、immutable貸与の挙動に影響しない。
fn main()
{
let a = 100;
{
let b = &a;
{
let c = &*b;
}
}
let mut a = 100;
{
let b = &a;
{
let c = &*b;
}
}
}
Borrower自体の借用元Ownerを変更しても、Borrowerが生きている限り、以前のOwnerによる使用(読み取り含む)は許されない。
let mut a = 100;
let mut b = 200;
{
let mut c = &mut a;
c = &mut b;
a; // NG
// => error: cannot use `a` because it was mutably borrowed
b; // NG
// => error: cannot use `b` because it was mutably borrowed
c = 300; // OK
}
a = 400; // OK
b = 500; // OK
mutable貸与中のオブジェクトは、Ownerによる一切の使用(読み取り、書き込み、mutable貸与、immutable貸与、所有権の移動)が許されない。
struct Point {
x :i32,
y :i32,
}
fn main()
{
let mut a = Point{ x:100, y:200 };
{
let b = &mut a;
a.x; // NG
// =>error: cannot use `a.x` because it was mutably borrowed
a.x = 200; // NG
// => error: cannot assign to `a.x` because it is borrowed
let c = &mut a; // NG
// => error: cannot borrow `a` as mutable more than once at a time
let d = &a; // NG
// => error: cannot borrow `a` as immutable because it is also borrowed as mutable
let e = a; // NG
// => error: cannot move out of `a` because it is borrowed
}
}
上の制約より、次の制約が帰結する。すなわち、一つのオブジェクトをmutable borrowing中に貸与できるのは、(immutable貸与であろうが、mutable貸与であろうが)同時に高々1回まで。以前のBorrowerが死んではじめて、新たなBorrowingが可能となる。
let mut a = 100;
{
{
let b = &mut a;
let c = &mut a; // NG
// => error: cannot borrow `a` as mutable more than once at a time
}
let c = &mut a; // OK
*c = 200; // OK
}
a = 300; // OK
Borrowerが、Borrower自体の所有権を移動したとき、移動先が新たなBorrowerとなる。また、(Borrower自体の)所有権の移動により、Borrowerがその時点で持っていた借用は、(借用がmutableであることも含めて)新たなBorrowerに委譲される。
ただし、借用が委譲されてもImmutable Borrowing中であることには変わりないので、委譲のタイミングではOwnerにオブジェクトは返却されない。返却されるのは、新たなBorrowerが死んだ瞬間である。
// a --(借用)--> b --(b自体の所有権)--> c は
// a --(借用)--> c と等価となる。
let mut a = 100;
{
let b = &mut a;
let c = b;
a; // NG
// => cannot use `a` because it was mutably borrowed
*b; // NG
// => error: use of moved value: `b`
*c = 200; // OK
}
a; // OK
a = 300; // OK
貸与した所有権を明示的に取り戻すことはできない。やはり、Borrowerが死ぬのを待つしかない。
let mut a = 100;
{
let b = &mut a;
a = *b; // NG
// => error: cannot assign to `a` because it is borrowed
}
a = 200; // OK
構造体の複数のメンバ変数を、個別にmutable貸与することは可能。ただし、メンバ変数をmutable貸与中に、それが属する構造体変数をimmutable/mutable貸与することや、構造体変数をmutable貸与中に、そのメンバ変数をimmutable/mutable貸与することはできない。
struct Point {
x :i32,
y :i32,
}
fn main()
{
let mut a = Point{x:100, y:200};
{
let b = &mut a.x; // OK
let c = &mut a.y; // OK
let d = &a; // NG
// => error: cannot borrow `a` as immutable because `a.y` is also borrowed as mutable
}
}
##まとめると
借用中オブジェクトへのアクセス可否について、表にまとめてみました。行見出し(縦)がオブジェクトのimmutabe/mutableを示し、列見出し(横)が借用時のimmutable/mutable指定を示します。
(RO … Read Only RW … Read Write)
Immutable Borrowing |
Mutable Borrowing |
|
---|---|---|
Immutable Owner |
Owner : RO Borrower : RO Immutable Subletting : OK Mutable Subletting : × Forbidden |
× Forbidden |
Mutable Owner |
Same as above | Owner's access: × Forbidden Borrower : RW Immutable Subletting : OK Mutable Subletting : OK |
こうしてみると意外と単純で、
- immutableなオブジェクトは、immutableな借用のみ可能である。
- mutableなオブジェクトは、immutable/mutableいずれの借用も可能である。
- immutableな借用は、オブジェクトのimmutable/mutableに関係なく、誰にとってもRead Onlyである。
- immutableで借用しているオブジェクトを、immutableで又貸しすることは可能である。ただし、mutableな又貸しはできない。
- mutable借用中の、immutableまたはmutableな又貸しは可能である。
- mutableなオブジェクトがmutable借用された場合、BorrowerによるRead Write、mutable貸与が可能である。
- Borrowerが、借用中オブジェクトの所有権を移動することはできない。
- 所有権の移動は、貸出中でない時にOwnerによって可能。
- mutable借用中のオブジェクトに対する、Ownerからのアクセスは一切不可能。
ということになります。
##さらにまとめると
- 所有権の移動に関して可能なのは、「Ownerが、貸与中でないObjectの所有権を移動すること」のみ。
- immutablly borrowed objectは、write以外のすべての操作が可能である。
- mutablly borrowed objectは、Ownerによるすべての操作が禁止され、Borrowerによるすべての操作が許可される。
すべての操作とは、Read、Write、Lendingを指す。