flag: add All, AllSet iterators
要約
概要
flagパッケージにコールバック方式のVisit/VisitAllに代わる、Go 1.23で導入されたrange-over-func(イテレータ)に対応した新しいイテレータAPIを追加する提案です。あわせて、フラグが「設定済みかどうか」を扱いやすくするためのAPI変更も議論されています。
ステータス変更
(新規) → active
2026年7月8日の週次Proposal Reviewミーティングで、本Issue(#65675)が正式にactiveカラムへ追加され、今後のレビュー対象となりました。なお、ほぼ同内容の重複提案であった#80026(FlagSet.All() iter.Seq2[*Flag, bool])は同じミーティングで「duplicate」として declined となり、議論はこの#65675に一本化されました。
技術的背景
現状の問題点
flagパッケージのVisit・VisitAllはコールバック関数を渡す古いスタイルのイテレーション方式で、boolを返さないため、Go 1.23で追加されたrange-over-func(for rangeに関数を渡せる仕組み)と互換性がありません。また、あるフラグが「明示的に設定されたか」を調べるには、内部的にformal(全フラグ)とactual(設定済みフラグ)という2つのマップを突き合わせる必要があり、利用者側では両方を走査してsetを判定する煩雑なコード(例: flagx.listVisitedFlagNames)を書かざるを得ませんでした。
提案された解決策
当初はAll() iter.Seq[*Flag]とAllSet() iter.Seq[*Flag]という2種類のイテレータを追加する案でしたが、議論の結果、レビューチーム(@aclements, @bradfitz)から以下のシンプルな設計が提示されました。
type Flag struct {
Name string
Usage string
Value Value
DefValue string
IsSet bool // 追加: このフラグが設定済みかどうか
}
func All() iter.Seq[*Flag]
func (*FlagSet) All() iter.Seq[*Flag]
Flag構造体にIsSet boolフィールドを追加することで、iter.Seq2[*Flag, bool]のような複雑な戻り値にせず、単一のiter.Seq[*Flag]だけで「全フラグ」と「設定状態」の両方を扱えるようにする案です。命令形のSetという名前は将来(*Flag).Set(value string)のような設定用メソッドのために温存し、フィールド名はIsSetとすることで区別しています。
これによって何ができるようになるか
for range構文で直接フラグ集合をループでき、Visit/VisitAllのコールバック地獄や、設定済みフラグを判定するための二重ループが不要になります。特に、サブコマンド実装で「あるフラグがどのFlagSetに属し、かつ設定済みか」を判定するような用途(例: CLIツールでグローバルフラグとサブコマンドフラグを区別するエラーチェック)で有用です。
コード例
// Before: Visit + VisitAll を組み合わせて判定する煩雑な書き方
var err error
cmdFlags.Visit(func(f *flag.Flag) {
if globalFlags.Lookup(f.Name) == nil {
err = fmt.Errorf("flag -%s belongs to subcommand but is placed before it", f.Name)
}
})
if err != nil {
return err
}
// After: All() と IsSet フィールドを使った書き方(レビューで議論中の案)
for f := range cmdFlags.All() {
if f.IsSet && globalFlags.Lookup(f.Name) == nil {
return fmt.Errorf("flag -%s belongs to subcommand but is placed before it", f.Name)
}
}
議論のハイライト
- 当初案(
All/AllSetの2イテレータ)に対し、@mvdan(コアメンバー)は「formalとactualという2つの内部マップを両方参照する必要がある」と指摘し、マップ統合をしない限り2つの独立イテレータの方が実装が簡潔であると述べた。 - 重複提案#80026では
iter.Seq2[*Flag, bool]案(Allが(*Flag, bool)のペアを返す)が提示されたが、@aclementsは「Flag.Set boolフィールドを追加してシンプルなiter.Seq[*Flag]にする方が良いのでは」とコメントし、こちらの方向にレビューが収束した。 - @bradfitzが命名について、フィールド名を
SetではなくIsSetとすることで、将来(*Flag).Set(value string)という命令形メソッドを追加する余地を残す案を提示し、コアチームで合意された。 - Issue作成者の@earthboundkidは「あるFlagが複数のFlagSetに属する可能性がある」ことを懸念し、
IsSetをフィールドではなくFlagSet.IsSet(name string) boolという関数にすべきと提案したが、@aclementsは「Flagを複数のFlagSetに追加する手段が現状APIには存在しないため、単一のFlagSetにのみ属する」と回答し、フィールド案が維持される見込み。 - 重複していた#80026は「duplicate」として declined され、議論は本Issue(#65675)に一本化された。