Description
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
)
- Prior to a change in Swift 3, return-values could be ignored by default (but a warning could be added with
- 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