Thoughts on the “guard” proposal for Go's error handling
I don&#x27;t think the article&#x27;s `CopyFile` function is a good example for either `guard` statements or exceptions.<p>We can come up with better examples (feel free), but I&#x27;m going to focus on that since it&#x27;s the one used in the article.<p>As a library consumer, I would like to know if the error happened in the source file or the destination file, ideally without requiring me to know implementation details of the function, so I don&#x27;t have to worry if the implementation changes and now my `if errors.Is(err, fs.PathError)` no longer works and instead became a bug.<p>For example, the caller might want to:<p>* Show a user-friendly message to the user if the problem is in the source file.<p>* Return gracefully if there&#x27;s not enough space to create the destination file (e.g. &quot;copy as many files as you can to this drive, and tell me which ones you copied&quot;).<p>* &quot;Bubble-up&quot; the error in any other situation.<p>A function that lets me know which file was the cause of the problem might look like this:<p><pre><code>    type ErrSourceFile struct{ error }
    type ErrDestFile struct{ error }

    func CopyFile(src, dst string) error {
        r, err := os.Open(src)
        if err != nil {
            return &amp;ErrSourceFile{error: err}
        }
        defer r.Close()

        w, err := os.Create(dst)
        if err != nil {
            return &amp;ErrDestFile{error: err}
        }
        defer w.Close()

        if _, err := io.Copy(w, r); err != nil {
            return err
        }
        if err := w.Close(); err != nil {
            return &amp;ErrDestFile{error: err}
        }

        return nil
    }
</code></pre>
Since now errors are actually useful to the caller function, the `guard` statements became unnecessary everywhere except in the `guard io.Copy(w, r)` case, because now we must handle nils (though I guess with generics we can create a wrapper that allows doing stuff like `guard WrapIfNonNil[ErrSourceFile](err)`, but whether that&#x27;s better is debatable).<p>If we tried to do this with exceptions so that we can actually catch them, the code would become:<p><pre><code>    type SourceFileException exception
    type DestFileException exception

    func CopyFile(src, dst string) throws error {
        &#x2F;&#x2F; Now necessary because the `try` blocks introduce new scopes.
        var (
            r io.ReadCloser
            w io.WriteCloser
        )

        try {
            r = os.Open(src)
        } catch err {
            &#x2F;&#x2F; Wrap exception because we don&#x27;t want to hide
            &#x2F;&#x2F; the inner exception.
            throw SourceFileException(err)
        }
        defer r.Close()

        try {
            w = os.Create(dst)
        } catch err {
            throw DestFileException(err)
        }
        defer w.Close()

        io.Copy(w, r)

        &#x2F;&#x2F; The caller might want to check the hash to decide if it
        &#x2F;&#x2F; should delete the file or not.
        try {
            w.Close()
        } catch err {
            throw DestFileException(err)
        }
    }
</code></pre>
... which is even more cumbersome than plain `if` blocks.<p>Exceptions have their advantages in certain cases, but they also have their downsides, it all depends on the situation and what you want to do.<p>As everything, it&#x27;s a trade-off.