メインコンテンツへスキップ

Go Proposal Weekly Digest

Go言語のproposal更新を毎週お届け

#77075declined

os/exec: add Cmd.Clone method

ステータス変更: active declined

要約

AIによる要約であり、誤りを含む場合があります。

概要

os/exec.Cmd に対して Clone() メソッドを追加することで、同一コマンドの設定を再利用して新しい Cmd インスタンスを安全に作成できるようにするという提案です。提案者自身が設計上の問題点を認識し最終的に撤回したため、「declined as retracted(撤回による却下)」となりました。

ステータス変更

activedeclined
提案者の @adonovan 氏が「Cmd は設定フィールドと実行状態が混在した構造体であり、Clone メソッドはむしろ問題の温床になる」と結論付け、2026年5月13日に自ら提案を撤回しました。翌日、@aclements 氏がプロポーザルレビューグループを代表して「declined as retracted」として正式にクローズしました。

技術的背景

現状の問題点

Go 1.26に向けて、exec.CmdStart メソッドを2回呼び出した際の誤用検出が強化されました(CL 728642、関連: #76746)。この変更で内部フィールドに sync/atomic のbool値が追加されたため、go vetcopylocks 静的解析チェッカーが Cmd の値コピーに対して警告を出すようになりました。
Start 失敗後に同じ Cmd を再利用したいケース(例: 一時的なエラーからのリトライ)では、安全な回避策として新しい Cmd を手動で構築する必要がありました。

// 問題のある従来のコード(Start失敗後に再利用しようとする)
cmd := exec.Command("myapp", args...)
cmd.Env = append(os.Environ(), "FOO=bar")
err := cmd.Start()
if err != nil {
    // Startに失敗した場合でも同じcmdを再利用しようとすると問題が発生
    err = cmd.Start() // エラー: exec: already started
}
// 現在のワークアラウンド(フラジルな手動コピー)
cmd2 := &exec.Cmd{
    Path: cmd.Path,
    Args: cmd.Args,
    Env:  cmd.Env,
    // ...全エクスポートフィールドを列挙する必要あり
    // 新フィールド追加時に対応漏れが生じやすい
    // さらにctxフィールドはエクスポートされておらず、コピー不可能
}

提案された解決策

package exec // "os/exec"
// Clone returns a new Cmd that is a copy of the old one with respect to
// its configuration, and on which Start has never been called.
func (*Cmd) Clone() *Cmd

全エクスポートフィールドと、CommandContext で設定される非エクスポートの ctx フィールドを含めた設定情報をコピーし、Start が呼ばれていない新しい Cmd を返します。net/http.Request.Clone に類似したパターンを想定していました。

これによって何ができるようになるか

この提案が実現していた場合の想定ユースケースは以下の通りです(実際には採用されませんでした)。

  • コマンドのリトライ: 一時的な失敗後に同一設定で再実行
  • 共通設定の雛形: ベースとなる Cmd から複数のバリアントを派生
  • テンプレート的な使用: 共通の環境変数・パスを設定済みの Cmd を複製

議論のハイライト

  • atomic.Bool の使用が直接のきっかけ: Start の多重呼び出し検出のために内部で sync/atomic を使ったところ、copylocks チェッカーが既存のコードに警告を出し始めた。aclements の提案で後に atomic.Bool から通常の bool に戻し(CL 734200)、警告問題は切り離された。
  • Std{in,out,err} フィールドの扱いが未解決の核心問題: Clone が全フィールドをコピーする場合、bytes.Buffer は既に書き込まれた内容を引き継ぐ、パイプは元の Cmd の状態に依存する、strings.Reader は巻き戻されないなど、I/O関連フィールドの意味論が複雑すぎると判明。
  • コンテキストのコピー問題: 非エクスポートの ctx フィールドをコピーする場合、最初の Cmd がコンテキストのタイムアウトで失敗した際、新しい Cmd にそのキャンセル済みコンテキストが引き継がれてしまう。かといって Clone(ctx context.Context) のようにパラメータを取ると、元のコンテキストを再利用したいケースに対応できない。
  • Cmd は設定と実行状態の混合体: adonovan 氏の最終結論は「Cmd が設定と実行中の状態を混在させている構造体である以上、Clone メソッドは問題の温床(can of worms)になる」というものであった。
  • 代替アプローチへの方向転換: 提案者は最終的に、「Cmd を非コピー可能として明示する」「新しい Cmd の生成ロジックはアプリケーション側でファクトリ関数として分離する」という方針を推奨して提案を撤回した。

関連リンク