😽
&mutをレシーバにしたメソッドでの所有権について
初めに
Rust触ってたところ、所有権でハマってしまったのでメモ。
&mutを引数にした場合の所有権について
参照をfnの引数にした場合、immutableな参照&
ならCopy Traitを実装しているので、moveしても参照をコピーしてくれる。しかし、mutableな参照&mut
はCopyを実装していないので、引数にすると所有権が移動してしまう。ここを理解しないで実装すると、参照がmoveされているのでコンパイルエラーになることがある。
この記事では、実装例と対処方法をまとめる。
例1 mutでない参照の場合
ここでは、Option
のmap
メソッドを使って例を挙げてみる。
このメソッドはレシーバがself
になっている。(つまりレシーバがmoveされる。)
pub fn map<U, F>(self, f: F) -> Option<U>
where
F: FnOnce(T) -> U,
例2 mutな参照の場合
レシーバがimmutableな参照&
の場合、map
メソッド呼び出し時にレシーバの参照がコピーされる。
元の参照はそのまま残っているため、メソッド呼び出し後もレシーバの参照を使用することができる。
#[derive(Debug, PartialEq, Eq)]
struct Sample<'a, T>(Option<&'a T>);
fn main() {
let binding = 1;
let sample = Sample(Some(&binding));
let sample2 = sample.0.map(|s| *s + 1);
assert_eq!(sample, Sample(Some(&1)));
assert_eq!(sample2, Some(2));
}
mutableな参照&mut
はCopy traitを実装しておらず、参照がmoveされてしまうため、map
を呼び出すとその後から参照できなくなる。(moveした参照をmapが消費してしまう)
#[derive(Debug, PartialEq, Eq)]
struct Sample<'a, T>(Option<&'a mut T>);
fn main() {
let mut binding = 1;
let mut sample = Sample(Some(&mut binding));
let sample2 = sample.0.map(|s| *s + 1);
assert_eq!(sample, Sample(None)); // コンパイルエラー
assert_eq!(sample2, Some(2));
}
エラー内容
error[E0382]: borrow of partially moved value: `sample`
--> src/main.rs:9:5
|
7 | let sample2 = sample.0.map(|s| *s + 1);
| -------- --------------- `sample.0` partially moved due to this method call
| |
| help: consider calling `.as_ref()` or `.as_mut()` to borrow the type's contents
8 |
9 | assert_eq!(sample, Sample(None));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ value borrowed here after partial move
|
note: this function takes ownership of the receiver `self`, which moves `sample.0`
--> /Users/hanaokachiiku/.rustup/toolchains/stable-x86_64-apple-darwin/lib/rustlib/src/rust/library/core/src/option.rs:919:28
|
919 | pub const fn map<U, F>(self, f: F) -> Option<U>
| ^^^^
= note: partial move occurs because `sample.0` has type `std::option::Option<&mut i32>`, which does not implement the `Copy` trait
= note: this error originates in the macro `assert_eq` (in Nightly builds, run with -Z macro-backtrace for more info)
対処方法
対処方法は色々あるが(正直あまり知らない)、一つ挙げるとtake()
を使用する方法がある。
take()
を使用するとレシーバから値を取り出してNone
に置き換えてくれるので、エラーを回避することができる。(値を変更するので、当然レシーバはmutにしなければならないことに注意。)
#[derive(Debug, PartialEq, Eq)]
struct Sample<'a, T>(Option<&'a mut T>);
fn main() {
let mut binding = 1;
let mut sample = Sample(Some(&mut binding));
let sample2 = sample.0.take().map(|s| *s + 1);
assert_eq!(sample, Sample(None));
assert_eq!(sample2, Some(2));
}
終わりに
雑な説明になってしまいましたが、ツッコミどころや疑問あればコメントいただけると嬉しいです!
よろしくお願い致します。
Discussion