53
31

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

所有権と借用について

Last updated at Posted at 2015-12-06

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

53
31
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
53
31

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?