I don't think the article's `CopyFile` function is a good example for either `guard` statements or exceptions.
We can come up with better examples (feel free), but I'm going to focus on that since it's the one used in the article.
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'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.
For example, the caller might want to:
* Show a user-friendly message to the user if the problem is in the source file.
* Return gracefully if there's not enough space to create the destination file (e.g. "copy as many files as you can to this drive, and tell me which ones you copied").
* "Bubble-up" the error in any other situation.
A function that lets me know which file was the cause of the problem might look like this:
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's better is debatable).
If we tried to do this with exceptions so that we can actually catch them, the code would become:
type SourceFileException exception
type DestFileException exception
func CopyFile(src, dst string) throws error {
// Now necessary because the `try` blocks introduce new scopes.
var (
r io.ReadCloser
w io.WriteCloser
)
try {
r = os.Open(src)
} catch err {
// Wrap exception because we don't want to hide
// 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)
// The caller might want to check the hash to decide if it
// should delete the file or not.
try {
w.Close()
} catch err {
throw DestFileException(err)
}
}
... which is even more cumbersome than plain `if` blocks.
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.
We can come up with better examples (feel free), but I'm going to focus on that since it's the one used in the article.
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'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.
For example, the caller might want to:
* Show a user-friendly message to the user if the problem is in the source file.
* Return gracefully if there's not enough space to create the destination file (e.g. "copy as many files as you can to this drive, and tell me which ones you copied").
* "Bubble-up" the error in any other situation.
A function that lets me know which file was the cause of the problem might look like this:
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's better is debatable).
If we tried to do this with exceptions so that we can actually catch them, the code would become:
... which is even more cumbersome than plain `if` blocks.
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.
As everything, it's a trade-off.