Today, if a Go function returns both a value and an error, the user must either assign the error to a variable
v, err := computeTheThing()
or explicitly ignore it
v, _ := computeTheThing()
However, errors that are the only return value (as from io.Closer.Close or proto.Unmarshal) or paired with an often-unneeded return value (as from io.Writer.Write) are easy to accidentally forget, and not obvious in the code when forgotten.
tx.Commit() // Store the transaction in the database!
The same problem can occur even when errors are not involved, as in functional-style APIs:
t := time.Now()
t.Add(10 * time.Second) // Should be t = t.Add(…)
strconv.AppendQuote(dst, "suffix") // Should be dst = strconv.AppendQuote(…)
For the few cases where the user really does intend to ignore the return-values, it's easy to make that explicit in the code:
_, _ = fmt.Fprintln(&buf, v) // Writes to a bytes.Buffer cannot fail.
go func() { _, _ = fmt.Fprintln(os.Stderr, findTheBug()) }()
And that transformation should be straightforward to apply to an existing code base (e.g. in a Go 1-to-2 conversion).
On the other hand, the consequences of a forgotten error-check or a dropped assignment can be quite severe (e.g., corrupted entries stored to a production database, silent failure to commit user data to long-term storage, crashes due to unvalidated user inputs).
Other modern languages with explicit error propagation avoid this problem by requiring (or allowing API authors to require) return-values to be used.
- Swift warns about unused return values, but allows the warning to be suppressed if the
@discardableResult attribute is set.
- Prior to a change in Swift 3, return-values could be ignored by default (but a warning could be added with
@warn_unused_result)
- Rust has the
#[must_use] attribute.
- C++17 has the
[[nodiscard]] attribute, which standardizes the longstanding __attribute__((warn_unused_result)) GNU extension.
- The
ghc Haskell compiler provides warning flags for unused results (-fwarn-unused-do-bind and -fwarn-wrong-do-bind).
- OCaml warns about unused return values by default. It provides an
ignore function in the standard library.
I believe that the OCaml approach in particular would mesh well with the existing design of the Go language.
I propose that Go should reject unused return-values by default.
If we do so, we may also want to consider an ignore built-in or keyword to ignore any number of values:
go func() { ignore(fmt.Fprintln(os.Stderr, findTheBug())) }()
or
go func() { ignore fmt.Fprintln(os.Stderr, findTheBug()) }()
or
go func() { _ fmt.Fprintln(os.Stderr, findTheBug()) }()
If we use a built-in function, we would probably want a corresponding vet check to avoid subtle eager-evaluation bugs:
go ignore(fmt.Fprintln(os.Stderr, findTheBug())) // BUG: evaluated immediately
Related proposals
Extending vet checks for dropped errors: #19727, #20148
Making the language more strict about unused variables in general: #20802
Changing error-propagation: #19991
Today, if a Go function returns both a value and an error, the user must either assign the error to a variable
or explicitly ignore it
However, errors that are the only return value (as from
io.Closer.Closeorproto.Unmarshal) or paired with an often-unneeded return value (as fromio.Writer.Write) are easy to accidentally forget, and not obvious in the code when forgotten.The same problem can occur even when errors are not involved, as in functional-style APIs:
For the few cases where the user really does intend to ignore the return-values, it's easy to make that explicit in the code:
And that transformation should be straightforward to apply to an existing code base (e.g. in a Go 1-to-2 conversion).
On the other hand, the consequences of a forgotten error-check or a dropped assignment can be quite severe (e.g., corrupted entries stored to a production database, silent failure to commit user data to long-term storage, crashes due to unvalidated user inputs).
Other modern languages with explicit error propagation avoid this problem by requiring (or allowing API authors to require) return-values to be used.
@discardableResultattribute is set.@warn_unused_result)#[must_use]attribute.[[nodiscard]]attribute, which standardizes the longstanding__attribute__((warn_unused_result))GNU extension.ghcHaskell compiler provides warning flags for unused results (-fwarn-unused-do-bindand-fwarn-wrong-do-bind).ignorefunction in the standard library.I believe that the OCaml approach in particular would mesh well with the existing design of the Go language.
I propose that Go should reject unused return-values by default.
If we do so, we may also want to consider an
ignorebuilt-in or keyword to ignore any number of values:or
or
If we use a built-in function, we would probably want a corresponding
vetcheck to avoid subtle eager-evaluation bugs:Related proposals
Extending
vetchecks for dropped errors: #19727, #20148Making the language more strict about unused variables in general: #20802
Changing error-propagation: #19991