How about separating check
and handle
as follows:
-
handle
can just works likedefer
as the draft suggested(called after return in reversed order of definition), and the magic identifiererr
could be changed to any error varible decalared, not the result ofcheck
. -
The error result as the last return value of a function should not be omitted in the presence of
check
. -
The return statements in
handle
just return thehandle
scope, not the function scope. -
Restrict functions to use named result parameters when using
check
.
func CopyFile(src, dst string) (err error) {
var err1, err2 error
handle err1, err2 { //at least one is non-nil
...
}
handle err1 {
err = fmt.Errorf("prepare copy %s %s: %v", src, dst, err1)
}
handle err2 {
err = fmt.Errorf("copy %s %s: %v", src, dst, err2)
}
r, err1 := check os.Open(src)
defer r.Close()
w, err1 := check os.Create(dst)
handle err2 {
w.Close()
os.Remove(dst)
}
err2 = check io.Copy(w, r)
// or need additional handling before returning to handles above
if err2 = io.Copy(w, r); err2 != nil {
... special handling ...
return
}
err2 = check w.Close()
}
Or in the simplest case:
func CopyFile(src, dst string) (err error) {
handle err {
err = fmt.Errorf("copy %s %s: %v", src, dst, err)
}
r, err := check os.Open(src)
defer r.Close()
w, err := check os.Create(dst)
handle err {
w.Close()
os.Remove(dst)
}
err = check io.Copy(w, r)
err = check w.Close()
}
In this way, check
works just equivalently to:
if err != nil {
return
}
handle
works just equivalently to:
defer func() {
if err != nil {
}
}()