Rustのstd::mem::replaceの使い方
背景
このドキュメントに沿ってRustで単連結リストを書いていて、リストに要素を追加する際、既存のリストの先頭要素を新しく追加する要素のnext
フィールドに付け替えようとしたところ、以下のようなエラーが発生した。
cannot move out of
self.head
which is behind a mutable reference
use std::mem;
pub struct List {
head: Link,
}
type Link = Option<Box<Node>>;
struct Node {
elem: i32,
next: Link,
}
impl List {
pub fn new() -> Self {
Self { head: None }
}
// `List` の先頭に `Node` を追加する。
pub fn push(&mut self, elem: i32) {
let node = Box::new(Node {
elem,
// cannot move out of `self.head` which is behind a mutable reference
next: self.head,
});
self.head = Some(node);
}
}
エラーメッセージによると、可変参照(&mut
)を通じての所有権移動が許可されていないということらしい。
要するに、参照であるself
からその要素をmoveすることはできないということである。
self.head.clone()
としても良いが、このようなケースにおいて一般に対象がclone
を実装しているとは限らないので、いつでもclone
できるとは限らない。
本題
これはよく知られたエラーらしく、std::mem::replace
を使うことで解決できる。
pub fn push(&mut self, elem: i32) {
let node = Box::new(Node {
elem,
next: mem::replace(&mut self.head, None),
});
self.head = Some(node);
}
replace
は以下のようなシグネチャを持っており、dest
の値をsrc
で書き換え、書き換える前のdest
の値を返す関数である。
pub fn replace<T>(dest: &mut T, src: T) -> T
replaceの実装を見てみよう。非常にシンプルで、説明の余地はほとんどなさそうである。
pub const fn replace<T>(dest: &mut T, src: T) -> T {
// SAFETY: We read from `dest` but directly write `src` into it afterwards,
// such that the old value is not duplicated. Nothing is dropped and
// nothing here can panic.
unsafe {
let result = ptr::read(dest);
ptr::write(dest, src);
result
}
}
唯一、戻り値の所有権について気になるところだが、 ptr::readのドキュメントによると、ptr::read<T>
は
read
creates a bitwise copy ofT
と書いてあるので、readの戻り値は所有権付きであることがわかる。
ちなみに、read(&s)
はs
と同一のメモリを指すため、read
の戻り値とs
を同時に使用するのは危険である旨が書かれている。
具体的には、let s2 = read(&s)
とした後にs2
に他の値を再代入すると、根底にあるメモリはdrop
されてしまうため、それ以降s
に再代入しようとすると根底にあるメモリに対して再びdrop
が呼ばれることになり、未定義動作を引き起こす。
ちなみにちなみに、今回の例のようにOption
を使う場合には、take
というラッパー関数が使える。
pub const fn take(&mut self) -> Option<T> {
// FIXME replace `mem::replace` by `mem::take` when the latter is const ready
mem::replace(self, None)
}
pub fn push(&mut self, elem: i32) {
let node = Box::new(Node {
elem,
next: self.head.take(),
});
self.head = Some(node);
}
Discussion