Repository: go
Issue: proposal: encoding/env: populate a struct from environment variables
Labels: Proposal

### Proposal Details

Today, programs can query environment variables using methods in `os`: `os.Getenv`, `os.Environ`, `os.LookupEnv`, etc.

```go
foo := os.Getenv("FOO")
bar, found := os.LookupEnv("BAR")
all := os.Environ()
```

This works and is very simple, but can get more complicated if you need to further validate or especially convert the string values to other types.

```go
e := os.Getenv("NUM_FOOS")
if e == "" {
    // NUM_FOOS must be set!
}
i, err := strconv.Atoi(e)
if err != nil {
    // NUM_FOOS must be parseable as an int!
}
```

If a program relies on many environment variables to configure its behavior, a common smells can creep in: authors call `os.Getenv` from deep within their code, which can make it hard to test ([`T.Setenv` helps](https://pkg.go.dev/testing#T.Setenv))

The alternative, slightly beter, is to populate a struct at the top of their program from env vars, and pass around this struct after validation/conversion is done.

https://github.com/kelseyhightower/envconfig is a very popular, very simple module to make this second approach simpler, by populating a struct from env vars, with type conversion and basic validation built in and configurable using struct tags.

Before:

```go
e := os.Getenv("NUM_FOOS")
if e == "" {
    // NUM_FOOS must be set!
}
i, err := strconv.Atoi(e)
if err != nil {
    // NUM_FOOS must be parseable as an int!
}
... for each env var to process
```

After:

```go
var env struct {
    Debug       bool
    Port        int    `required:"true"`
    User        string `default:"foobar"`
    Users       []string
    Rate        float32
    Timeout     time.Duration
    ColorCodes  map[string]int
}
if err := envconfig.Process("", &env); err != nil {
    // Something went wrong!
}
```

The second arg lets callers pass a prefix to env vars, so using `envconfig.Process("MY", &env) would populat `Timeout` from `MY_TIMEOUT`.

`envconfig` currently has 12k+ dependents on GitHub: https://github.com/kelseyhightower/envconfig/network/dependents

It has no dependencies outside of stdlib: https://github.com/kelseyhightower/envconfig/blob/10e87fe9eaec671f89425dc366f004a9336bcc8f/go.mod

I propose that some functionality like this be included in the stdlib directly.

---

There may be functionality included in `envconfig` that Go authors don't consider worth including in stdlib, or would implement or expose differently, and IMO that's totally fine.

I'm mainly opening this to get feedback and gauge interest in such a thing being included by default.

Personally I'd propose dropping the `MY_` prefixing feature, and rename the package to `os/env` (or just `env`? Or include it as `os.ProcessEnv`?). If the `required` and `default` struct tags make it, they should be namespaced like `env:"required"` etc.

If multiple values failed validation, all the errors should be returned with [`errors.Join`](https://pkg.go.dev/errors#Join).

There's also support for custom decoders -- perhaps those should leverage [`TextUnmarshaler`](https://pkg.go.dev/encoding#TextUnmarshaler)?