🤔

RustのOwnershipに対する自分なりの理解

2023/02/22に公開

TL;DR

  • Rustでは、変数それぞれにOwnerが存在する。
  • 1つの変数に付きOwnerは1人のみ。
  • Owenerがスコープ外に移ると、そのOwnerが所有していた変数は解放される。
  • Each value in Rust has an owner.
  • There can only be one owner at a time.
  • When the owner goes out of scope, the value will be dropped.

悪い例1:shallow copyしてしまってエラー

問題

shallow_copy_sample.rs
fn main() {
    let s1 = String::from("bad_copy sample");
    let s2 = s1;

    println!("{}, world!", s1); // s1の所有権がs2に移動したあとにs1を参照するとエラー
    println!("{}", s2);  // s2を使うことはOK
}
error[E0382]: borrow of moved value: `s1`
 --> copy.rs:5:28
  |
2 |     let s1 = String::from("bad_copy sample");
  |         -- move occurs because `s1` has type `String`, which does not implement the `Copy` trait
3 |     let s2 = s1;
  |              -- value moved here
4 |
5 |     println!("{}, world!", s1); // s1の所有権がs2に移動したあとにs1を参照するとエラー
  |                            ^^ value borrowed here after move
  |
  = note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info)
help: consider cloning the value if the performance cost is acceptable
  |
3 |     let s2 = s1.clone();
  |                ++++++++

error: aborting due to previous error

For more information about this error, try `rustc --explain E0382`.

let s2 = s1;でs1の所有権はs2に移り、s1はdropされる。
そのため、s1を参照するとエラーになる。

解決策:deep copyしよう

let s2 = s1.clone();と明示的にdeep copyすれば、コンパイルが通る。

deep_copy_sample.rs
fn main() {
    let s1 = String::from("hello deep copy");
    let s2 = s1.clone();

    println!("s1={}", s1);
    println!("s2={}", s2);
}
s1=hello deep copy
s2=hello deep copy

悪い例2:メソッドに渡したあとの変数を参照してしまってエラー

問題

fn main() {
    let s = String::from("hello");  // s comes into scope
    takes_ownership(s);             // s's value moves into the function... and so is no longer valid here
    takes_ownership(s); // これはエラー!

    let x = 5;                      // x comes into scope
    makes_copy(x);                  // x would move into the function, but i32 is Copy, so it's okay to still use x afterward
    makes_copy(x); //これはOK
}

fn takes_ownership(some_string: String) { // some_string comes into scope
    println!("{}", some_string);
} // Here, some_string goes out of scope and `drop` is called. The backing memory is freed.

fn makes_copy(some_integer: i32) { // some_integer comes into scope
    println!("{}", some_integer);
} // Here, some_integer goes out of scope. Nothing special happens.
error[E0382]: use of moved value: `s`
  --> take_owenership_sample.rs:4:21
   |
2  |     let s = String::from("hello");  // s comes into scope
   |         - move occurs because `s` has type `String`, which does not implement the `Copy` trait
3  |     takes_ownership(s);             // s's value moves into the function... and so is no longer valid here
   |                     - value moved here
4  |     takes_ownership(s); // これはエラー!
   |                     ^ value used here after move
   |
note: consider changing this parameter type in function `takes_ownership` to borrow instead if owning the value isn't necessary
  --> take_owenership_sample.rs:11:33
   |
11 | fn takes_ownership(some_string: String) { // some_string comes into scope
   |    ---------------              ^^^^^^ this parameter takes ownership of the value
   |    |
   |    in this function
help: consider cloning the value if the performance cost is acceptable
   |
3  |     takes_ownership(s.clone());             // s's value moves into the function... and so is no longer valid here
   |                      ++++++++

error: aborting due to previous error

For more information about this error, try `rustc --explain E0382`.

takes_ownership(s);を2回読んだ場合、1回目の呼び出し時点でs所有権はtakes_ownershipに移る。そのため、二回目の呼び出しはエラーになる。

解決策:s.clone()でメソッドに渡そう

// ...
takes_ownership(s.clone());
takes_ownership(s.clone());
// ...
hello
hello
5
5

ちなみに:i32は気にしなくていい

makes_copy(x)は2回呼び出しても問題ない。
integerはheapではなくstackにpushされ、xyはそれぞれ別物になるため、Stringのときのような参照の不整合が起きないためである。
(integerはサイズが予めわかっているため(高々2^32byte)、heapではなくstackで管理される。)

良い例:メソッド内でreturnすれば、所有権は返ってくる

fn main() {
    let s1 = gives_ownership();         // gives_ownership moves its return value into s1
    println!("s1={}", s1);

    let s2 = String::from("hello");     // s2 comes into scope
    println!("s2={}", s2);

    let s3 = takes_and_gives_back(s2);  // s2 is moved into takes_and_gives_back, which also moves its return value into s3
    println!("s3={}", s3);
} // Here, s3 goes out of scope and is dropped. s2 was moved, so nothing happens. s1 goes out of scope and is dropped.

fn gives_ownership() -> String {             // gives_ownership will move its
                                             // return value into the function
                                             // that calls it

    let some_string = String::from("yours"); // some_string comes into scope

    some_string                              // some_string is returned and
                                             // moves out to the calling
                                             // function
}

// This function takes a String and returns one
fn takes_and_gives_back(a_string: String) -> String { // a_string comes into
                                                      // scope

    a_string  // a_string is returned and moves out to the calling function
}
s1=yours
s2=hello
s3=hello

メソッドに渡した変数であっても、returnすることで所有権を呼び出し元に返すことができる。

コラム:スタックとヒープ

  • stack・・・push/popで引き出すもの。LIFO。プレートを一枚ずつ重ねていくイメージ。
  • heap・・・広大な領域から任意のサイズの領域を確保し、その領域へのポインターをstackで管理する。レストランのテーブル予約のイメージ。

アクセス速度は、stackのほうが速い。heapはメモリ領域確保に時間を要するのでどうしても遅くなる(stack -> pointer -> heapと辿る)。stackはpopすればいいだけなので高速。

参考:

https://doc.rust-lang.org/book/ch04-01-what-is-ownership.html

Discussion