mcpField
O
ownership-not-clone

ownership-not-clone

github.com/rewrite-rs開発1042 回インストール10 週間前に更新
リポジトリ

ownership-not-clone について

借用チェッカーのエラーを clone で回避せず、値の所有者と借用期間を決める Rust 向けのコーディングスキルです。実際の要件に応じて、参照やフィールドごとの借用、共有所有権を選びます。

主な機能

  1. 01コンパイラーエラーから借用競合を診断
  2. 02不要な所有権より参照を選択
  3. 03フィールドごとに可変借用を分割
  4. 04借用と所有を切り替える Cow の使用
  5. 05共有所有権の必要性を評価
  6. 06Clippy とテストで変更を検証

活用シーン

  1. 01コンパイルエラー対策の clone を見直す
  2. 02呼び出し側の clone を避ける関数シグネチャ
  3. 03可変操作で別々のフィールドを借用
  4. 04共有状態に Rc や Arc が必要か判断

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/rewrite-rs/skills --skill ownership-not-clone

インストール後、Claude Code で/ownership-not-cloneを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

CLONE-DECISIONS.md
SHARED-STATE.md
agents/
openai.yaml

詳細

所有権を意識する。クローンで済ませない

借用チェッカーは回避すべき障害ではなく、所有権をどこで曖昧にしているかを示す地図です。このスキルでは、誰が値を保持し、どれくらいの期間保持するかを決めます。

ルール

すべての clone は、「借用チェッカーに文句を言われたから」ではない一文で説明できなければなりません。借用期間を超えて値を保持する必要があるなど、実質的なものを得られるクローンなら問題ありません。一方、エラーを黙らせるだけのクローンは、設計上の判断を先送りしているにすぎません。

回避策ではなくエラーを読む

E0502(不変借用がまだ有効な間の可変借用)と E0499(同じ場所への2つの可変借用)は、ライフタイムの競合を示しています。解決策はエラーを黙らせる clone ではなく、エラーが示す構造にあります。

// No clone: `first` is Copy, so the borrow ends at the let.
let first = items[0];
for _ in 0..n {
    items.push(first);
}

借用の分割

借用チェッカーは、メソッド呼び出しを通じて値全体を追跡しますが、フィールドへの直接アクセスではフィールドごとに追跡します。self.other_field も必要とする &mut self メソッドは、典型的な誤検出による競合です。一度分解しましょう。

// The two fields are separate lets and borrow independently.
impl Server {
    fn handle(&mut self) {
        let Self { connections, log, .. } = self;
        for conn in connections.iter_mut() {
            log.record(conn.id());
        }
    }
}

split_at_mut は、1つのスライスから互いに重ならない2つの &mut を返します。分解だけでは不十分な場合は、2つのフィールドを受け取るフリー関数を切り出してください。

引数には、要件を満たす最も軽い型を使う

関数が読み取るだけのパスには、&String ではなく &str、&Vec<T> ではなく &[T]、PathBuf ではなく impl AsRef<Path> を使います。読み取るだけなのに String を受け取るシグネチャでは、すべての呼び出し元にクローンを強いることになります。問題はシグネチャにあります。

// Forces a clone at every call site that holds a &str.
fn log_message(message: String) { ... }

// Reads only — callers pass a reference and copy nothing.
fn log_message(message: &str) { ... }

本当に借用または所有する場合の Cow

一般的な経路では入力をそのまま返し、まれな経路でのみ割り当てを行う場合、Cow<'_, str> を使う価値があります。

use std::borrow::Cow;
// The common path borrows; only input containing a bracket allocates.
fn sanitize(input: &str) -> Cow<'_, str> {
    if input.contains(['<', '>']) {
        Cow::Owned(input.replace('<', "&lt;").replace('>', "&gt;"))
    } else {
        Cow::Borrowed(input)
    }
}

Rc/Arc/RefCell/Mutex が本当の要件を表す場合

共有所有権が妥当なのは、実行時の構造に本当に複数の所有者がいて、どれか1つの所有者がほかの所有者より長く存続することもない場合です。単一の所有者を名前で表すのが面倒なだけの場合ではありません。RefCell を追加する前に、SHARED-STATE.md にある4つの質問によるテストを実施してください。判断表は CLONE-DECISIONS.md にあり、一文で説明できるテストに合格するハンドルのクローンは SHARED-STATE.md にあります。

mem::take と mem::replace

mem::take と mem::replace は、元の場所にデフォルト値を残すことで、&mut から値を移動させます。「この値を所有する必要があるが、手元にあるのは可変借用だけ」という場合の解決策です。

let old = mem::take(&mut self.buffer);

static を避ける

可変状態や遅延初期化状態を保持する static は、誰も明記していないライフタイムであり、誰もたどれない所有権の説明です。値を渡すか、その処理を担う型に保持させてください。

先送りする範囲

正しいコードの表現方法を整える内容は /idiomatic-rust、不変条件を型にモデル化する内容は /type-driven-design、エラー型は /rust-errors、.await ポイントをまたぐ共有状態は /async-rust を参照してください。

検証

クローンを残すか削除するかで動作は変わらないため、「これでも動く」という主張は、リポジトリで設定されている lint のレベルに応じてテストスイートで確認します。

cargo clippy --all-targets   # add -- -D warnings only if the repo has no lint config
cargo test

clippy::redundant_clone は nursery に属し、デフォルトでは無効です。提案の生成に限り、cargo clippy -- -W clippy::redundant_clone を一度だけ実行してください。リポジトリの lint 設定には追加しないでください。検出結果は編集候補であり、修正そのものではありません。

コメント