所有権と借用について

  • 21
    Like
  • 0
    Comment
More than 1 year has passed since last update.

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を指す。