😽

&mutをレシーバにしたメソッドでの所有権について

2023/02/11に公開

初めに

Rust触ってたところ、所有権でハマってしまったのでメモ。

&mutを引数にした場合の所有権について

参照をfnの引数にした場合、immutableな参照&ならCopy Traitを実装しているので、moveしても参照をコピーしてくれる。しかし、mutableな参照&mutはCopyを実装していないので、引数にすると所有権が移動してしまう。ここを理解しないで実装すると、参照がmoveされているのでコンパイルエラーになることがある。

この記事では、実装例と対処方法をまとめる。

例1 mutでない参照の場合

ここでは、Optionmapメソッドを使って例を挙げてみる。
このメソッドはレシーバがselfになっている。(つまりレシーバがmoveされる。)
https://doc.rust-lang.org/stable/std/option/enum.Option.html#method.map

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にしなければならないことに注意。)
https://doc.rust-lang.org/stable/std/option/enum.Option.html#method.take

#[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