借用チェッカーを満足させるためのクローン

説明

借用チェッカーは、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 を実行するようにしてください。これは1234 のような clone() が不要なケースをいくつか検出します。

See also

Last change: 2024-07-09, commit: 317c88e