From 80b07a745f5713f11d24360b1e0863eef98ac842 Mon Sep 17 00:00:00 2001 From: Abhinav Gupta Date: Fri, 12 Aug 2022 09:28:55 -0700 Subject: [PATCH] Document named return constraint for defer (#63) 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. 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. --- error.go | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/error.go b/error.go index 9a43f21..5c2c9e1 100644 --- a/error.go +++ b/error.go @@ -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 // @@ -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 // @@ -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: @@ -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 } @@ -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. @@ -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) } @@ -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 := // ... @@ -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()) }