flag: add All iterators and Flag.IsSet
要約
概要
flagパッケージに、Goのrange-over-func(iter.Seq)に対応した新しいイテレータAll()と、フラグが実際にセットされたかどうかを示すFlag.IsSetフィールドを追加する提案です。
ステータス変更
likely_accept → accepted
2026年7月15日の提案レビューで「likely accept」となった後、2026年7月22日に実装CL(go.dev/cl/804020)が提出され、大きな異論もなかったことから、7月23日のレビューで「no change in consensus」として正式にacceptedとなりました。
技術的背景
現状の問題点
flagパッケージには既にVisit(セットされたフラグのみ)とVisitAll(全フラグ)というコールバック形式のイテレータメソッドがありますが、これらはfunc(*Flag)を受け取るだけでboolを返さないため、Go 1.23で導入されたrange-over-func(iter.Seq)と互換性がありません。そのため、以下のような煩雑なクロージャとエラー変数の組み合わせが必要でした。
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
}
また、あるフラグが「セットされているか」を判定する公式なAPIが存在せず、内部的にはformal(定義済み)とactual(セット済み)という2つのmapで管理されているため、外部から判定するにはVisitとVisitAllの結果を突き合わせる必要がありました。
提案された解決策
議論は当初、単一のAll() iter.Seq2[*Flag, bool](boolでセット状態を表す)案と、All()/AllSet()の2イテレータ案の間で揺れましたが、最終的にはよりシンプルな設計に落ち着きました。Flag構造体にIsSet boolフィールドを追加し、All()は*Flagのみを返すiter.Seq[*Flag]とすることで、イテレータ自体をシンプルに保ちつつ状態情報も取得可能にしています。
type Flag struct {
// ...既存フィールド...
IsSet bool // このフラグがセットされたかどうか
}
func All() iter.Seq[*Flag]
func (*FlagSet) All() iter.Seq[*Flag]
これによって何ができるようになるか
for range構文で直接フラグを走査できるようになり、コードが簡潔になります。また、IsSetフィールドにより、サブコマンド境界の検証、環境変数によるデフォルト値の上書き判定、未セットフラグの一覧表示など、セット状態に依存する処理が書きやすくなります。
コード例
// Before: Visitのコールバックとエラー変数を組み合わせる必要がある
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)
}
})
// After: range-over-funcと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)
}
}
議論のハイライト
- 当初(2024年4月)は
All()とAllSet()という2つの独立したイテレータが提案されたが、命名(特に"AllSet")に不満が残った。 - @earthboundkidが
iter.Seq2[*Flag, bool]案を提示したが、@mvdanが「formalとactualの2つのmapを両方参照する必要があり実装コストが増す」と指摘し議論が停滞。 - 2026年6月、同種の提案(#80026、@adonovan提出)がレビューボードに載ったことをきっかけに本issueの議論が再燃。プロセスの一貫性・透明性の欠如についても言及された。
- @aclementsが「
FlagにIsSet boolフィールドを追加しAll()はシンプルなiter.Seq[*Flag]にすべき」と提案し、これが採用の軸となった。 - @bradfitzが命名を
SetではなくIsSetとすることを提案。将来的に(*Flag).Set(value string)という命令的メソッドを追加する余地を残すための配慮。 Flagが複数のFlagSetに属する可能性についての懸念が挙がったが、Flagは生成元のFlagSetにのみ紐づくため問題ないと確認された。- 実装CL提出後、
Flag構造体へのフィールド追加によりgo/analysis/passes/compositeのテストが失敗し、別CL(go.dev/cl/804080)で修正された。