🤔
RustのOwnershipに対する自分なりの理解
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され、x
とy
はそれぞれ別物になるため、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すればいいだけなので高速。
参考:
Discussion