要約
- Rustで開発している上で所有権が渡せなくて困るときに、可変参照を取れるなら安全に回避するすべがある。
- でもライブラリを作るときは使えなかった所有権を返すようなAPIが望ましい。
所有権で困る
Rustで開発していると所有権で困ることがあります。
自分は外部crateを使っているときに所有権を要求されて渡せなくて困りました。
clap-serdeなるライブラリを書いてたときにあたった問題として、
- clapは
Command::mut_argでCommandの所有権を要求される。(引数が&mut selfでなくてselfである) - serdeは
de::SeqAccess::next_element_seedではseedに所有権を渡すが残りがないと帰って来ない。- 残りがあれば渡した構造体が帰ってくるのでそのままそっちのリソースを握ればいいが、なければ
Option::Noneが帰ってしまう。
- 残りがあれば渡した構造体が帰ってくるのでそのままそっちのリソースを握ればいいが、なければ
let seed = Seed::new();
loop {
let new_seed : Option<Seed> = seq.next_element_seed(seed)?;
// このあとseedにnew_seedを入れて更新したいが、
// OptionなのでNoneのときにもとのデータが消えてしまう
}
seed//返り値
- Seedに
Commandをラップしたものを渡してデシリアライズ中にmut_argに所有権を渡したいが残りがない状態で渡してしまうと、Command自体の所有権が消えてしまう。
このようになり、Command::mut_argに所有権を渡せなくなってしまいました。
安全な対処法
今回は所有権自体は渡す手前まで保持しているので簡単に解決できます。
mem::takeを使います。
可変参照として渡してやればtakeして所有権を持った状態にできます。
もちろんその後そこに返してあげないといけませんが。
//渡すとき
let new_seed : Option<()> = seq.next_element_seed(&mut seed)?;
//受け取るとき
let value = std::mem::take(seed);//takeで中身を奪って所有権を得る。
let value_2 = value.func_take_resource();//所有権を奪って新しく返すような関数
*seed = value_2;//中身を返す
このようにしてもshared xor mutexのおかげでどこかでDefaultを入れただけのseedにアクセスされることはないことが保証されています。
mem::takeは型がDefaultであることを要求していて、つまり代わりにDefault値が入ります。
例えばDefaultが実装されていなかったりdefault関数が重かったりするときはOption::takeを代わりに使えます。自分はこちらを使いました。ただこちらの場合Optionのハンドリングが余計に必要です。
理想的な解決法
今回は外部クレートを使っている関係で上のような実装になりましたが、理想としては所有権を奪ったリソースを使わなかったら返すほうがいいです。
例えばOnceCell::setのように失敗したときにResult<(),T>でErrとしてもとのリソースを所有権付きで返したり、SyncSender::try_sendのようにエラーの中にもとのリソースを保持した状態で返すなどのことをしてあげるといいです。
//serdeも以下のように定義してあれば問題なかった。
fn next_element_seed<T>(
&mut self,
seed: T
) -> Result<Result<T::Value, T>, Self::Error>
//けど現実は Result<Option<T::Value>, Self::Error>
このように所有権を消費しようとして失敗したときに返す型はOptionではなくResultにしましょう。