借用チェッカーを満足させるためのクローン
説明
借用チェッカーは、Rustユーザが安全でないコードを開発することを、次のことを確保することで防ぎます: 1つだけの可変参照が存在するか、もしくは幾つでもよいがすべて不変である参照が存在すること。もし書かれたコードがこれらの条件を満たさない場合に、コンパイルエラーを解決するために開発者が変数のクローンを作成したとき、このアンチパターンが発生します。
例
#![allow(unused)] fn main() { // 変数を定義 let mut x = 5; // `x` を借用 -- ただし、先だってクローンする let y = &mut (x.clone()); // 2行前の x.clone() 無しでは、xが借用されているためこの行がコンパイルエラーになります。 // x.clone() により、 x は借用されていないため、この行が動作します。 println!("{x}"); // Rust がこれを消去する形の最適化を防ぐため、何らかの操作をします。 *y += 1; }
動機形成
特に初心者の方にとっては、借入チェッカーによるややこしい問題を、このパターンを使って解決したくなるものです。しかし、ここには深刻な結果があります。.clone()
を使うと、データのコピーが作成されます。この2つの間の変更は同期されません – 完全に別の2つの変数が存在することと同じです。
特別なケースがあります – Rc<T>
はクローンをインテリジェントに扱うように設計されています。内部的にデータのコピーを1つだけ持っており、クローンを作成しても参照だけをクローンします。
また、ヒープに確保されたT型の値に対する共有所有権を提供する Arc<T>
もあります。Arc
に対して .clone()
を呼び出すと、新しい Arc
インスタンスが生成されますが、このインスタンスは元とヒープの同じ値を指しています。また同時に、参照カウンタがインクリメントされます。
一般に、クローンはその影響を十全に理解し、意図的に行われるべきものです。もしも借用チェッカーのエラーを消すためにクローンが使われてるなら、それはこのアンチパターンの使われている可能性を示す兆候と言えます。
.clone()
は悪しきパターンの表れですが、ときに 非効率なコードを書いてもよい こともあります。次のようなケースです:
- 開発者が所有権に慣れていない
- コードの速度やメモリに大きな制約がない場合 (ハッカソンプロジェクトやプロトタイプなど)
- 借用チェッカーを満足させることが本当に複雑で難しく、パフォーマンスよりも読みやすさを最適化したい
不必要なクローンが疑われる場合、Rust Book の所有権の章 を十分に理解した上で、クローンが必要かどうかを判断してください。
また、プロジェクト内で常に cargo clippy
を実行するようにしてください。これは1、 2、 3、 4 のような clone()
が不要なケースをいくつか検出します。