skip to content

After flag.Parse, how can a Go program tell which flags were actually set on the command line?

level: middleimportance: nice to knowfreq 30%

answer

  1. one walks all, one walks only set
  2. order matters: after Parse, not before
  3. the callback receives a flag descriptor
  4. DefValue is the default as a string
  5. Visit means it appeared on the command line

basics

~20 s

Call flag.Visit after flag.Parse: it invokes your function only for flags that were set on the command line. flag.VisitAll walks every declared flag, set or not, which is what you want for dumping the effective configuration.

solid answer

~40 s

`flag.Visit` and `flag.VisitAll` differ in exactly one way: `Visit` calls your function only for flags that were actually set during parsing, `VisitAll` calls it for every flag that was declared. Both iterate in lexicographical order and hand you a `*flag.Flag` carrying `Name`, `Usage`, `Value` and `DefValue`, the string form of the default. So `flag.Visit` after `flag.Parse` is how you ask what the operator typed — useful when a later layer, say a config file or an environment variable, must not overwrite an explicit flag. `flag.VisitAll` is how you print the effective configuration at startup, secrets redacted. The trap is calling `Visit` before `Parse`: nothing has been set yet, so it reports nothing. Comparing a value to its default is not a substitute, since an operator may pass exactly the default value.

code

go · 12 lines
go
flag.Parse()

set := map[string]bool{}
flag.Visit(func(f *flag.Flag) { set[f.Name] = true }) // only flags actually given

if !set["timeout"] {
	// no -timeout on the command line, so a lower-priority layer may fill it in
}

flag.VisitAll(func(f *flag.Flag) { // every declared flag, set or not
	log.Printf("config %s=%s (default %s)", f.Name, f.Value, f.DefValue)
})

go deeper

for a junior

Remember the pair and the one difference: flag.Visit walks only what was set, flag.VisitAll walks everything declared. Both must run after flag.Parse to mean anything useful.

for a middle

Explain the callback's *flag.Flag fields and why Visit is the correct test for 'the operator typed this', rather than comparing the value against the default or the zero value.

for a senior

Show where you actually reach for this: applying a lower-priority layer without stomping explicit flags, and printing the effective configuration once before the service starts listening, with secrets redacted.

for a principal

Decide what every service must expose about its own configuration at startup, and who is allowed to see it. A consistent effective-config line is a support tool; an unredacted one is an incident.

## Two iterators, one difference The `flag` package exposes two walkers over the flags of the default command-line set: ```go func Visit(fn func(*Flag)) func VisitAll(fn func(*Flag)) ``` `VisitAll` calls `fn` for **every flag that was declared**. `Visit` calls it only for the flags that were **set** — meaning they appeared on the command line and `Parse` assigned them. Both iterate in lexicographical order by flag name, which is also why `-h` output is alphabetical. Each call hands you a `*flag.Flag`: - `Name` — the flag name without the leading dash. - `Usage` — the help text you supplied. - `Value` — a `flag.Value`, whose `String` method renders the current value, so `%s` prints it. - `DefValue` — the **string form of the default**, captured when the flag was declared. There is also `flag.NFlag()`, the count of flags that were set, and `flag.Lookup(name)` to fetch one flag by name. ## What Visit is actually for The common use is provenance. Suppose a setting can come from a config file, an environment variable, or a flag, and the file is read *after* the command line is parsed — for example because the file's path is itself a flag. You now need to apply the file's values without stomping anything explicit. Build a set of what was typed: ```go flag.Parse() set := map[string]bool{} flag.Visit(func(f *flag.Flag) { set[f.Name] = true }) ``` and skip any key present in that set when applying the lower-priority layer. This is the escape hatch for cases where you cannot simply seed the flag's default before parsing. The second use is reporting: a startup line naming which settings the operator overrode is often the fastest answer to "why is this instance behaving differently". ## Why value comparison is not a substitute The instinct is to compare a variable to its default and call it "unset". That is wrong twice. First, an operator is allowed to pass exactly the default value on purpose — `-workers=4` when 4 is the default — and that is an explicit choice you would erase. Second, if you seeded the default from an environment variable, the "default" you would compare against is itself the environment's value, so the comparison no longer means anything. Comparison against the zero value is worse still: `0`, `""` and `false` are legitimate settings. `DefValue` has the same subtlety and it is worth stating plainly: it records the default **as it was at declaration time**. If you seeded that default from `os.LookupEnv`, `DefValue` shows the environment's value, not the compiled-in one. That makes a `VisitAll` dump honest about what would have been used, but it does not tell you which layer supplied it — if you need that, record the source yourself while resolving. ## Ordering rules `Visit` before `Parse` reports nothing, because no flag has been set yet; that is not an error and produces no warning, which is what makes it an easy bug. `VisitAll` before `Parse` works fine and shows every declared flag with its default — useful for generating documentation, useless for reporting effective values. And neither one sees a flag declared after `Parse` ran; late declarations are a bug the package will not save you from. ## Printing the effective configuration ```go flag.VisitAll(func(f *flag.Flag) { log.Printf("config %s=%s (default %s)", f.Name, f.Value, f.DefValue) }) ``` Two cautions. Redact anything secret — a token or a database password passed as a flag is already visible in the process table, and logging it makes it permanent. And print this **before** the service starts accepting traffic, so the line is at the top of the log when someone is bisecting a bad rollout, not buried after the first request. ## A note on scope Both functions operate on the package-level flag set that `flag.String`, `flag.Duration` and friends populate. Anything you build on top of them — provenance maps, effective-config dumps, a required-flags check that exits when a mandatory flag was never set — is a few lines over these two iterators and needs no library.

  • Why not just compare a flag's value to its default to decide whether it was set?
    Because passing the default value explicitly is legal and meaningful, so comparison erases a deliberate choice. And if the default was itself seeded from an environment variable, you are comparing against that value rather than the compiled-in one, which makes the test meaningless. `flag.Visit` reports assignment, not equality.
  • What is flag.Flag.DefValue, and what does it show when the default came from an environment variable?
    `DefValue` is the string form of the default recorded when the flag was declared. If you seeded that default from the environment, `DefValue` shows the environment's value, not the compiled-in one. It is honest about what would be used, but it does not identify which layer supplied it — track the source yourself if you need to report it.
  • How would you make a flag mandatory using these functions?
    Declare it with a default that is invalid on purpose, parse, then build the set of names from `flag.Visit` and check membership — or simply validate the resolved value afterwards. Report every missing setting at once and exit nonzero, rather than failing on the first one and making the operator rerun repeatedly.

saying these in an interview costs you the question

  • Calls flag.Visit before flag.Parse and concludes nothing was set
  • Thinks flag.VisitAll reports only the flags that were set
  • Infers 'unset' by comparing the value to the zero value
  • Dumps every flag at startup including secrets