Skip to content

Commit

Permalink
Document named return constraint for defer
Browse files Browse the repository at this point in the history
As discussed in #61, if you attempt to use `defer multierr.AppendInvoke`
with an error that is not a named return, the system will lose the
error.

```go
func fails() error { return errors.New("great sadness") }

func foo() error {
	var err error
	defer multierr.AppendInvoke(&err, multierr.Invoke(fails))
	return err
}

func main() {
	fmt.Println(foo()) // nil
}
```

https://go.dev/play/p/qK4NR-VYLvo

This isn't something the library can address because of how defers work.

This change adds a warning about the error variable being a named return
in all places where we suggest use of multierr with defer.

While we're at it, this makes use of the new `[Foo]` godoc syntax to
generate links to other functions in the package in "See Foo"
statements in the documentation.
  • Loading branch information
abhinav committed Aug 12, 2022
1 parent 492b792 commit a61c151
Showing 1 changed file with 18 additions and 4 deletions.
22 changes: 18 additions & 4 deletions error.go
Expand Up @@ -75,7 +75,7 @@
// This will append the error into the err variable, and return true if that
// individual error was non-nil.
//
// See AppendInto for more information.
// See [AppendInto] for more information.
//
// # Deferred Functions
//
Expand Down Expand Up @@ -107,7 +107,10 @@
// // ...
// }
//
// See AppendInvoke and Invoker for more information.
// See [AppendInvoke] and [Invoker] for more information.
//
// NOTE: If you're modifying an error from inside a defer, you MUST use a named
// return value for that function.
//
// # Advanced Usage
//
Expand Down Expand Up @@ -461,6 +464,9 @@ func Combine(errors ...error) error {
// defer func() {
// err = multierr.Append(err, f.Close())
// }()
//
// Note that the variable MUST be a named return to append an error to it from
// the defer statement. See also [AppendInvoke].
func Append(left error, right error) error {
switch {
case left == nil:
Expand Down Expand Up @@ -541,7 +547,7 @@ func AppendInto(into *error, err error) (errored bool) {
// AppendInvoke to append the result of calling the function into an error.
// This allows you to conveniently defer capture of failing operations.
//
// See also, Close and Invoke.
// See also, [Close] and [Invoke].
type Invoker interface {
Invoke() error
}
Expand All @@ -565,6 +571,9 @@ type Invoker interface {
// but defer the invocation of scanner.Err() until the function returns.
//
// defer multierr.AppendInvoke(&err, multierr.Invoke(scanner.Err))
//
// Note that the error you're appending to from the defer statement MUST be a
// named return.
type Invoke func() error

// Invoke calls the supplied function and returns its result.
Expand All @@ -588,6 +597,9 @@ func (i Invoke) Invoke() error { return i() }
// defer the invocation of f.Close until the function returns.
//
// defer multierr.AppendInvoke(&err, multierr.Close(f))
//
// Note that the error you're appending to from the defer statement MUST be a
// named return.
func Close(closer io.Closer) Invoker {
return Invoke(closer.Close)
}
Expand Down Expand Up @@ -618,6 +630,8 @@ func Close(closer io.Closer) Invoker {
// // ...
// }
//
// NOTE: If used with a defer, the error variable MUST be a named return.
//
// Without defer, AppendInvoke behaves exactly like AppendInto.
//
// err := // ...
Expand All @@ -642,7 +656,7 @@ func Close(closer io.Closer) Invoker {
// defer multierr.AppendInvoke(&err, multierr.Invoke(foo))
//
// multierr provides a few Invoker implementations out of the box for
// convenience. See Invoker for more information.
// convenience. See [Invoker] for more information.
func AppendInvoke(into *error, invoker Invoker) {
AppendInto(into, invoker.Invoke())
}

0 comments on commit a61c151

Please sign in to comment.