大抵の言語なら、こんなことは何も考えずにできるのですが、Rustでは安直にコードを書くと、borrow checkerさんに怒られます。
頻出パターンなのですが、解決法を探してもなかなか見つからなかったので、自分なりの解法をここに残そうと思います。
※Rustのバージョンは1.24.1 (stable)です。
#構造体から所有権を持っているフィールドを引っ剥がす
フィールドから所有権を引っ剥がした後は、なにか代わりの値を入れなければならないので、swap処理を実装します。
が、とりあえず安直に書くとこんな感じで怒られます。
#[derive(Debug)]
pub struct MyStruct<T> {
value: T
}
// NG
impl<T> MyStruct<T> {
pub fn swap(&mut self, newvalue: T) -> T {
let tmpvalue = self.value; // ←ここでコンパイルエラー
self.value = newvalue;
return tmpvalue;
}
}
error[E0507]: cannot move out of borrowed content
Rustでプログラムを書いているとよく出会うエラーです。
##エラーの回避方法
self
は借用ですので、勝手に所有権を渡して、元の構造体を破壊することはできません。上記のコードは最終的にはself.value
は新しい値で埋まりますが、処理途中でself.value
の所有権がなくなり、不正な状態となります。
では、参照でどうにかしよう…とすると、また怒られます。
// NG
impl<T> MyStruct<T> {
pub fn swap(&mut self, newvalue: T) -> T {
let ref tmpvalue = self.value;
self.value = newvalue; // ←ここでコンパイルエラー
return *tmpvalue; // ちなみにこれも借用しているオブジェクトの所有権を渡しているため、ダメ
}
}
error[E0506]: cannot assign to `self.value` because it is borrowed
self.value
は貸与中なので、潰しちゃダメと言われます。
そんなこんなでいろいろ試すも、合法な方法はなかなか見つかりません。
では、どのようにすれば可能なのでしょうか。
複製する
引っ剥がしたいフィールドがCopy
トレイトやClone
トレイトを実装している場合は、複製することでエラーを回避できます。
// OK
impl<T: Clone> MyStruct<T> {
pub fn swap(&mut self, newvalue: T) -> T {
let tmpvalue = self.value.clone(); // 値が複製される。
self.value = newvalue;
return tmpvalue;
}
}
ただし、複製の場合は(実装次第ですが)中身も完全に複製されます。例えばVec<T>
は中身の要素まで全て複製されます。あまり効率がよろしくありません。使える型も制約されます。
self
の所有権を放棄させる
メソッドがself
の所有権を奪えば、処理途中でself.value
の所有権がなくなっても、すぐに戻してあげれば問題なく動作します。
// OK
impl<T> MyStruct<T> {
pub fn swap(mut self, newvalue: T) -> (Self, T) {
let tmpvalue = self.value; // 構造体の所有権を持っているため、問題ない
self.value = newvalue; // ただし、フィールドをmoveした場合は、次に使うまでに所有権を埋めなければならない
return (self, tmpvalue);
}
}
Clone
のような完全複製は行われないのですが、メソッドの使い方が少し変わってしまうのが残念なところ。
std::mem::swap
を使う
一時的に発生する不正な状態をBorrow Checkerさんに見せなければ、エラーを回避できます。
// OK
impl<T> MyStruct<T> {
pub fn swap(&mut self, newvalue: T) -> T {
let mut tmpvalue = newvalue;
std::mem::swap(&mut self.value, &mut tmpvalue); // self.valueとtmpvalueを入れ替える
return tmpvalue;
}
}
self.value
の所有権がなくなる瞬間は、std::mem::swap
関数の中に隠蔽されているので、外から見れば合法です。では、std::mem::swap
関数の中はどうなっているのかと言うと、std::mem::swapのソースを見ると、unsafeコードとなっています。
&mut self
からフィールドを複製せずに所有権を引っ剥がすには、unsafeコードを書かなければならないのです。std::mem::swap
関数は、swap処理のunsafeな部分を担ってくれるので、外から見ればunsafeを意識しなくて済みます。
この方法だと、コピーも最小限で済むし、self
の所有権も元のまま保持させられるので、この方法を使うといいでしょう。
clone
とswap
の違い
本当に最小限のコピーで済んでいるのか検証のため、イケないコードを書きます。
fn main() {
let vsource = vec![1,2,3];
let mut mystruct = MyStruct{ value: vec![4,5,6]};
// mystruct.valueとスライスを共有するVecを作成する
let p = mystruct.value.as_mut_ptr();
let len = mystruct.value.len();
let cap = mystruct.value.capacity();
let v = unsafe {Vec::from_raw_parts(p, len, cap)};
println!("vsource: {:?}", vsource);
println!("mystruct: {:?}", mystruct);
println!("v: {:?}", v);
// mystruct.valueを引っ剥がす
println!("let mut vstruct = mystruct.swap(vsource)");
let mut vstruct = mystruct.swap(vsource);
println!("vstruct: {:?}", vstruct);
println!("mystruct: {:?}", mystruct);
println!("v: {:?}", v);
// 引っ剥がしたVecの要素を編集
println!("vstruct[2] = 0");
vstruct[2] = 0;
println!("vstruct: {:?}", vstruct);
println!("v: {:?}", v);
}
v
はswap
前に作成したvec![4,5,6]
のスライスを持っているので、このv
の変化に着目します。
では、clone
バージョンとswap
バージョンでそれぞれ実行してみます。
vsource: [1, 2, 3]
mystruct: MyStruct { value: [4, 5, 6] }
v: [4, 5, 6]
let mut vstruct = mystruct.swap(vsource)
vstruct: [4, 5, 6]
mystruct: MyStruct { value: [1, 2, 3] }
v: [4, 5, 6]
vstruct[2] = 0
vstruct: [4, 5, 0]
v: [4, 5, 6]
vsource: [1, 2, 3]
mystruct: MyStruct { value: [4, 5, 6] }
v: [4, 5, 6]
let mut vstruct = mystruct.swap(vsource)
vstruct: [4, 5, 6]
mystruct: MyStruct { value: [1, 2, 3] }
v: [4, 5, 6]
vstruct[2] = 0
vstruct: [4, 5, 0]
v: [4, 5, 0]
clone
バージョンでは、内部で持っているポインタが指す先も複製しているので、vstruct
を変更してもv
の値は全く変化しません。
対して、swap
バージョンは、vstruct
のスライスへの変更によってv
も変更されます。std::mem::swap
では、内部で持っているポインタが指す先は複製されていないことがわかります。
(追記)std::mem::replace
を使う
コメントで記事を紹介していただきました。
Rustの身代わりパターン - 簡潔なQ
その記事ではstd::mem::replace
を使ってますね。
replace
関数は目にしたことはあったのですが、戻り値が上書き前の値だということを見逃してました。
つまり、前述のstd::mem::swap
を使ったメソッドは、以下のようにしても問題なく動作します。
// OK
impl<T> MyStruct<T> {
pub fn swap(&mut self, newvalue: T) -> T {
// let mut tmpvalue = newvalue;
// std::mem::swap(&mut self.value, &mut tmpvalue);
// return tmpvalue;
std::mem::replace(&mut self.value, newvalue)
}
}
1行で済みますね。どちらを使うかは用途次第ですが、動作的にはほぼ同じです。
#構造体からイミュータブルな参照のフィールドを引っ剥がす
フィールドがイミュータブルな参照となっている場合は、先程の安直な方法で実装できます。
#[derive(Debug)]
pub struct MyStruct<'a, T: 'a> {
value: &'a T
}
// OK
impl<'a, T> MyStruct<'a, T> {
pub fn swap(&mut self, newvalue: &'a T) -> &'a T {
let tmpvalue = self.value;
self.value = newvalue;
return tmpvalue;
}
}
イミュータブルな参照は、複数持つことが許されているので、tmpvalue = self.value
は合法です。
また、std::mem::swap
を使う方法も可能です。
// OK
impl<'a, T> MyStruct<'a, T> {
pub fn swap(&mut self, newvalue: &'a T) -> &'a T {
let mut tmpvalue = newvalue;
std::mem::swap(&mut self.value, &mut tmpvalue);
return tmpvalue;
}
}
(追記)std::mem::replace
を使う
// OK
impl<'a, T> MyStruct<'a, T> {
pub fn swap(&mut self, newvalue: &'a T) -> &'a T {
std::mem::replace(&mut self.value, newvalue)
}
}
#構造体からミュータブルな参照のフィールドを引っ剥がす
フィールドがミュータブルな参照となっている場合は、先程の安直な方法では実装できません。
pub struct MyStruct<'a, T: 'a> {
value: &'a mut T
}
// NG
impl<'a, T> MyStruct<'a, T> {
pub fn swap(&mut self, newvalue: &'a mut T) -> &'a mut T {
let tmpvalue = self.value; // ←ここでコンパイルエラー
self.value = newvalue;
return tmpvalue;
}
}
error[E0507]: cannot move out of borrowed content
ミュータブルな参照は、唯一つしか存在できないため、tmpvalue = self.value
は違法です。
これも、std::mem::swap
を使う方法で解決可能です。
// OK
impl<'a, T> MyStruct<'a, T> {
pub fn swap(&mut self, newvalue: &'a mut T) -> &'a mut T {
let mut tmpvalue = newvalue;
std::mem::swap(&mut self.value, &mut tmpvalue);
return tmpvalue;
}
}
(追記)std::mem::replace
を使う
// OK
impl<'a, T> MyStruct<'a, T> {
pub fn swap(&mut self, newvalue: &'a mut T) -> &'a mut T {
std::mem::replace(&mut self.value, newvalue)
}
}
結論
構造体からフィールドの値を引っ剥がすメソッドは、std::mem::swap
を使えば実装可能。
#[derive(Debug)]
pub struct MyStruct<T> {
value: T
}
// OK
impl<T> MyStruct<T> {
pub fn swap(&mut self, newvalue: T) -> T {
let mut tmpvalue = newvalue;
std::mem::swap(&mut self.value, &mut tmpvalue); // self.valueとtmpvalueを入れ替える
return tmpvalue;
}
}
(追記)std::mem::replace
でもよりシンプルに実装可能。
// OK
impl<T> MyStruct<T> {
pub fn swap(&mut self, newvalue: T) -> T {
std::mem::replace(&mut self.value, newvalue)
}
}