
Why Go's Error Handling Is Awesome - michaelanckaert
https://rauljordan.com/2020/07/06/why-go-error-handling-is-awesome.html
======
throw_m239339
Go error handling is barely better than C error codes.

    
    
       int foo(int * return_value){ …; return 0 }
    

That's what Go does. There is no "error handling" baked into the language,
aside from panics, it's a convention. It's not "awesome", it's primitive. You
can do that in any language, and you don't when you have access to more
evolved constructs.

~~~
arp242
That's not exactly what Go does, as you can return much more information and
it's almost always clear what's an error and what isn't (there are a few
exceptions, such as strings.Index() returning -1).

~~~
throw_m239339
> That's not exactly what Go does, as you can return much more information and
> it's almost always clear what's an error and what isn't (there are a few
> exceptions, such as strings.Index() returning -1).

Yes it is exactly what Go does. Look, I'm "returning" an "out" parameter from
my C function here. I can also return an "out" error in form of a "struct *
error_return" if I wanted to, with a char * member or whatever.

This is not error handling baked in C either, just like Go. There is nothing
"awesome" with doing that. I could do that in any language and nobody would
call that "awesome".

------
leshow
You can get all these benefits and not have any of the downsides if you have
something like Result or Either, which requires generics. You only really
compare Go's solution against exceptions. That's not the only solution that
exists.

~~~
stefanchrobot
...or pattern matching in case of dynamically-typed languages:

    
    
      case some_operation() do
        {:ok, result} -> # do something with result
        {:error, error} -> # handle the error
      end

~~~
leshow
You lose the ability to make sure you've handled an error AOT, but sure, this
feature is pretty much the same as a sum type except that it's structural.
When I used to write typescript, there were plenty of times I was happy to
have it's union types!

------
andrewzah
Go's error handling is, frankly, terrible. It being better than some of the
competition doesn't mean it's good.

It's extremely verbose and relies on functions returning a value OR an error.
If I have an error in a function that returns a user or error, I still have to
do "return &User{}, err". Or if I have no error, then it's "return user, nil".
Ugly and verbose.

I really don't want to sound like a rust snob, but when it comes to errors and
error handling, I only really want to use rust because it has Result<T, E> and
Option<T> generic types. I don't have to do a million "if err ...", I can
simply use ? inside a function that returns a Result<T, E> and kick it down
the chain until I want to handle it. Then I can do a "match" statement on a
Result to unwrap it and handle all cases.

To give an example, look at how succinct this is. [0] In that function I make
3 calls that could give an error and just put a ? on two of them, and I handle
the last call manually with a match statement. Now compare that to this
function in golang [1], where I have to make several calls that could error.
(Admittedly this golang code could be better, but...)

Rust's system is not only more convenient but it forces me to handle error
cases. That strictness is very annoying when I just want code to compile, but
I'm sure it has saved me from plenty of bugs. With i.e. Ruby I ran into bugs
at runtime quite frequently because I didn't realize I wasn't handling X or Y
case. Golang at least has static types but it's still quite easy to forget to
handle certain cases.

I say this having written a fair amount of golang code and a lot of rust code.
Golang is not a bad language by any measure; I find myself reaching for it a
lot.

[0]: [https://github.com/azah/hangeul-
rs/blob/master/src/lib.rs#L3...](https://github.com/azah/hangeul-
rs/blob/master/src/lib.rs#L314)

[1]:
[https://github.com/azah/caddy_logingov/blob/master/handler.g...](https://github.com/azah/caddy_logingov/blob/master/handler.go#L175)

~~~
flohofwoe
Isn't Rust's error handling essentially the same as Go's except that Rust has
a bit of syntax sugar built around it? In Rust you still have a composite
return value which bundles an error code with a value-payload, it's just
wrapped in a Result<> type. So the whole thing is also just a convention, just
as in Go, but built around Rust's language features.

(PS: I agree that this sort of "explicit" error handling is vastly superior to
exceptions, but in my point of view, Rust and Go are pretty much identical, at
least when compared to exceptions).

~~~
andrewzah
For a Result<T, E> the returned type will either be an Ok(T) or an Err(E). Not
both, so it's not a composite type like golang with defining a Tuple of (T,
error). So you can then do a match statement like this:

match maybe_error() {

    
    
      Ok(t) => {}, // we succeeded
    
      Err(e) => {}, // we did not succeed
    

}

Result itself is an enum with Ok and Err variants. [0] Some functions use
Option<T> which will either be Some(T) or None.

[0]: [https://doc.rust-lang.org/std/result/index.html](https://doc.rust-
lang.org/std/result/index.html)

~~~
flohofwoe
Ok, this requires Rust's "rich enum type" (whatever that's called), and some
sort of generics, both of which Go doesn't have.

But you can create the "gist" of this sort of return-based error handling even
in C, by returning a struct which contains both an error code and a value-
payload, it's just not as convenient (because C lacks generics), or as "safe"
(because one can access the value even when an error is returned), but it
still would be better than "old-school" C code which splits the error-code and
success-value into a simple return value and an out-pointer, and arguably also
better than C++ exceptions.

~~~
throwaway8941
>"rich enum type" (whatever that's called)

Algebraic data types, or sum types, or discriminated unions.

GP also forgot to mention that Rust (among other languages with support for
ADTs) force you to handle all possible values, so you simply can't forget to
handle an error.

~~~
thatswrong0
> so you simply can't forget to handle an error.

This is such an important point, and I know people may think its outrageous
that this could happen, but I work with a bunch of talented programmers, and
still we've had the occasional bug crop up in our Golang codebase where we
have a `val, err = doSomething()` without an ensuing `if err != nil { }` check
which was missed by the programmer and by the reviewer. Or even better, we've
had `val, _ = doSomething()` occur, the result of debugging to get around the
unused variable compilation error.

It's 2020. Just like nil-pointers, this is an issue that is for the most part
_solved_ and shouldn't exist in modern languages. Our programming languages
should be constructed with the fact that we're fallible humans in mind.

------
pie_flavor
Reading this, I was visualizing in my head an article titled _Why Nulls Are
Good_ which started with "Look, you may not like the idea of values that may
or may not be present, but the truth is that a lot of programming relies on
it. Why imagine..." and going on and on about optional values and never once
addressing the real accusation about null, which is that it's a stupid _way of
accomplishing_ optional values.

Go's error handling is atrocious. Not because errors are return types, or
because of boilerplate for propagating them. Because they do not communicate
the most important invariant: that _either_ there is an error _or_ there is a
successful value. A tagged union is better. Exceptions are better. Even C's
error code system in a twisted way can be thought of as slightly better
because at least there is one dedicated spot for the error so you can shove
the function call in the if statement directly.

Not to mention, as an attack against exceptions, he writes code that logs
errors to the console and does nothing else, and then complains that it logs
errors to the console and does nothing else, as though that's a flaw in the
exception system and not in the code he just wrote. No shit, if you write Java
code to dump an unreadable stack trace, an unreadable stack trace is what's
getting dumped. How this eludes the author, or why he thinks it's a benefit to
not have a stack trace when you want one, is beyond me.

------
foxhill
oddly, the feature that is suggested as being awesome is in fact go's _worst_
feature, which is:

value tuples are not error handling.

it is not sufficient to rely on an IDE or linter to catch lack of error-case
handling. they are semantically identical, and in practise worse, to
exceptions: at least exceptions will bubble up, and be caught.. somewhere.

and i'm not saying exceptions are the solution. they really are awful. in my
opinion, 'either'/'oneof' types are the best solution here: if something
returns either a failure of a success value, then you as a developer are
forced to handle the failure condition:

    
    
        // pseudo c#
        either<error, int> result = get_string_length(str);
        return reslut.match(left: _ => 0, right: x => x);
     

you may choose to throw an exception, you may choose to return a default. but
you absolutely cannnot _not_ handle this function's failure condition (sorry,
double negative..).

~~~
arp242
Go communicates errors, and you can do something those errors (i.e. handle
them). Ergo, it has error handling. It may not be the kind of error handling
you like – which is perfectly reasonable – but that's not the same as Go not
having error handling.

~~~
cy_hauser
> communicates errors, and you can do something those errors

By this definition is there a language that doesn't have "error handling?"

------
threeseed
1\. In Java you have both checked and unchecked exceptions so you have the
option to force the developer to handle the error explicitly or not. With Go
there is no choice.

2\. Compared to something like ZIO ([http://zio.dev](http://zio.dev)) from the
Scala world, Go's error handling is amateurish. With ZIO you represent your
methods as such:

    
    
      def fetchFromDB: IO[DatabaseError, MyObject]
    

Now DatabaseError can be anything e.g. an exception, a string, an enum, a
custom error class or even nothing at all if there will never be an error. Now
you can chain functions together transforming errors along the way as if they
were normal values. Both powerful and elegant.

~~~
mtrovo
I don't know how this is better. Just looking at the signature I don't believe
this is more powerful or elegant than the same equivalent in Java: \- what
exactly the object is doing with your error, I assume this is an IO monad so
no changes are applied on the right side after an error but to be sure I would
have to look at the library doc and rely on it rather than having this
behavior enforced as part of the language design. \- it doesn't require you to
handle the exception or be clear that you handled it, for me this is a big
problem of using Either, it treats error as one alternative of the code rather
than something unusual that needs to be handled ASAP. If the compiler doesn't
enforce it 99% of the cases nobody care to treat it at the right application
layer which makes it very difficult to debug. \- not strictly about your
example but in Scala there's no standard way to handle exceptions, so if
you're required to integrate this code with a library outside ZIO that uses a
different pattern (java exceptions, Either, value classes, etc) it would start
to feel you're writing a lot of glue code that should be part of the language
design itself.

I still feel Java is the baseline on how to bring exceptions to the language
design and I believe part of the success of Java as a stable and predictable
language is because of it. I feel a lot of the times new programming languages
try to avoid java exception handling system because it's too verbose or
brittle just to arrive in some solution that trade out the verbose checked
code but doesn't deliver any feature on the same level.

~~~
esarbe
IO is a monad, so you get pretty nifty composition out-of-the-box already with
Scala. Since it's ZIO, you get even nicer error handling that you would with,
say, cats' IO, where you i.e. have to deal with nested Monads and need to use
Monad transformers, but that's another story.

> or me this is a big problem of using Either, it treats error as one
> alternative of the code rather than something unusual that needs to be
> handled ASAP.

But that depends on the use-case. There are several 'kinds' of errors and some
of them have to be treated - as you put it - "one alternative of the code". If
it's a communication error, you might want to retry instead of fail
immediately, for example. If you are validating user input, retrying doesn't
make sense, so there you want to fail fast.

> If the compiler doesn't enforce it 99% of the cases nobody care to treat it
> at the right application layer which makes it very difficult to debug.

Not quite sure what you mean by that. Care to rephrase?

> in Scala there's no standard way to handle exceptions,

But of course. It's `try (f()) catch { case e: .... }`. That's one and only
way to handle exceptions in Scala. After an exception has been caught, you of
course can lift the errors into any data structure you want (Try, Either, IO,
ZIO,...).

~~~
mtrovo
> Not quite sure what you mean by that. Care to rephrase? It should be hard or
> impossible to produce code that doesn't catch an exception or ignore it and
> just bubble the exception up on the call stack. The consequence of it is
> that sometimes it's easy to have a low level exception arriving on your
> presentation layer, which totally breaks encapsulation.

> But of course. It's `try (f()) catch { case e: .... }`. That's one and only
> way to handle exceptions in Scala. After an exception has been caught, you
> of course can lift the errors into any data structure you want (Try, Either,
> IO, ZIO,...). That may be true for Java Exceptions but not 100% true for the
> generic concept of errors. You have try-catch which was inherited from JVM
> but most of my experience working in Scala people would call it a bad
> pattern and prefer using Either monads. The problem then being that Either
> try to solve something on the user code space that is already solved on the
> JVM level. And in a very poor way, think how in Java you can do a try-catch
> with type unions on the catch clause, or how you can catch and easily
> transform an exception into a totally different one and declare your
> signature on the most restrictive exception type. And the list of
> awkwardness goes on, see if you would use the same strategy to handle
> exceptions on the following examples:

\- code dealing with Java libraries

\- code dealing with Futures

\- code dealing with Either or other monads with left/error side

\- code that have to interact with different helper libraries (like cats or
shapeless)

\- Either.map over a value that could produce a RuntimeException (e.g atoi)

------
seanwilson
The vast majority of my error handling is for failures you really can't do
anything about locally (e.g. network connection died, couldn't connect to
database, couldn't write to file system) so a few try/catches close to the
top-level of the program (instead of strewed across all your files) are pretty
ideal for me when working on e.g. frontend, games, simple backend APIs.

For cases like "user not found" and "incorrect password", ideally I'm going to
have proper option types that statically check I'm handling the
success/failure flows. Which "errors" go into option types and which errors go
into exceptions is a design choice that's dependent on the domain.

I'm guessing people that don't like exceptions are working in domains that
need lots of careful local error handling?

I'm not seeing where the problem is to be honest. Every approach can be abused
somehow if a programmer wants to excessively suppress errors.

------
phpnode
Go's error handling really reminds me of errbacks in node (callback function
with a possible error as first argument), with many of the same pitfalls. I
wonder why so many people defend Go's choices here in comparison?

~~~
BenoitEssiambre
"errbacks" definitely have benefits since having the error parameter forces
you to think "where should I handle this error". With exceptions, people often
get sloppy and let them be thrown and handled wherever, often at the wrong
place.

Now it does make code more verbose since 90% of the time, the answer to "where
should I handle this error" is "not right here" and with go/errbacks, you have
to be explicit in that case while it is the default for exceptions.

For Javascript, I recently wrote a module that allows you to jump between
styles very easily:
[https://github.com/bessiambre/casync#readme](https://github.com/bessiambre/casync#readme)

~~~
phpnode
The benchmarks in that library are misleading - by wrapping the resolve in the
promise example in a process.nextTick you're actually making the engine
enqueue 2 microtasks, as promises are always guaranteed to be async. If you
remove the process.nextTick the results are reversed, I get:

\- 63ms (async)

\- 100ms (casync)

so it's almost twice as slow as native async await in this example.

~~~
BenoitEssiambre
The main reason for this library has to do with simplification more than
performance but just for the sake of argument, in my little benchmark, the
point was to make the asynchronous operation identical in both tests. You can
use promises without any asynchronous operation inside but what's the point?
That's not how they're used most of the time. And sure promises may add a
second microtask around the asynchronous code (casync does too in some cases)
but that is to their detriment performance wise (and do promises really add a
second microtask when the code inside is already asynchronous?).

Also note that async has the benefit of language level integration and
optimization. It gets to use v8 c++ functions like "EnqueueMicrotask" instead
of process.nextTick. If casync had access to all this, it would likely run
even faster.

------
bovermyer
Ironically, Go's error handling is part of why I'm rewriting an application in
_PHP_ that I spent two years writing in Go. Not a big part, but a part.

~~~
navd
Care to elaborate on why it was such an issue?

~~~
bovermyer
Partly, it cluttered the code and decreased legibility.

Mostly, though, it was the debugging ecosystem that was the bigger issue with
error handling. With the version I'm writing in PHP/Laravel, stack traces are
very clear about where they happen and involving what components. When I plug
in Sentry, this makes debugging production issues much easier.

Go's code is more elegant, more performant, and is fun to write, but
maintaining PHP in production is much nicer and less time-consuming.

For a project like this, where it's just something I do in my spare time for
fun, not having to spend a lot of time managing production is important.

~~~
eadan
I agree, the lack of stack traces can be annoying when debugging Go programs.
Instead of returning raw errors everywhere, the problem can be alleviated by
adding some context to returned errors:

    
    
      if err := foo(x); err != nil {
          return fmt.Errorf("foo: bad argument %s", x)
      }

~~~
julvo
The problem with this is, that the caller of the function which returns what
you wrote (i.e. a dynamic error message) can't match the error anymore for
conditional error handling. That's because errors in go are just strings. I
guess a solution could be to provide an error matching function but that seems
quite cumbersome compared to typed errors.

~~~
arp242
You should never match against "err.Error()" for conditional error handling, a
better solution is to use a custom error type. There are a bunch of different
ways to approach this in Go.

~~~
apta
Except most libraries do nothing of the sort, so you end up having to do a
substring search.

~~~
arp242
I've rarely run in to this, but if you do then I'd expect most people will be
happy to accept a PR.

------
Smaug123
The author's arguments apply better to demonstrating that `Maybe` is good, in
my book. It's conceptually cleaner to pattern-match on the optional `err`
using a fully general `match` construct that works on arbitrary discriminated
unions, than using an `if` statement, as far as I'm concerned.

~~~
akavi
These discussions are always frustrating, because it feels like the people
advocating Go's approach are comparing them to Java style Checked Exceptions,
while the people most annoyed with Go's approach are usually comparing them to
Sum Types (like Rust's Result or Haskell's Maybe/Either).

It's made even more frustrating in that it's very difficult to convey the
pragmatic differences between those two approaches to someone who hasn't had
the chance to use both. Both _seem_ to be a way of capturing errors in the
type system, but the latter really is just dramatically more ergonomic.

So people talk past each other and around and around we go.

~~~
dgb23
I agree but with a caveat.

The Rust and Go error handling approach is only superficially different. Both:

\- are statically checked

\- stick out to the caller

\- are just values (instead of idiosyncratic control mechanisms)

\- allow the caller to handle them at their discretion

The big difference on how you interact with errors are orthogonal to that. In
Rust you get to use pattern matching and a whole bunch of useful traits and
sugar, both for propagating them and for panicking. In Go you write these if-
blocks. So in Rust you end up with something more involved/complex that
conveys its semantics clearer. Go is much more simple and accessible but also
less expressive. (Another good example of this is the whole story about
iterators in Rust vs. just for-loops in Go.)

From my perspective the difference in error handling is more about the
languages in general, because both just give you error-values. From a general
perspective. There certainly are subtleties, which I'm ignoring here.

~~~
steveklabnik
IMHO your overall thesis of "Go and Rust are very similar here" is correct,
but you're missing the largest difference, IMHO. In my mind, Go and Rust's
mechanisms are very close, except that:

* Go returns a product type, that is, you get a value AND an error

* Rust returns a sum type, that is, you get a value OR an error

This doesn't mean one or the other is inherently better, though I do admit a
strong personal bias for the Rust way. The tradeoff is basically that sum
types really need generics to work well, IMHO. This means that Go can drop a
lot of the other machinery Rust has that makes its system work. That's the
core tradeoff.

This matters because of, for example, your first bullet point. Yes, both
languages statically check their errors, but the ways in which they do so and
the properties you get from said static checking are significantly (in my
mind) different.

~~~
dgb23
Thank you, this is an important distinction and informs the different
structural idioms of handling these cases:

In Go you get a flat recipe of instructions. You find an error then exit the
function somehow, usually via returning. In the subsequent blocks the
soundness is sometimes only implied AKA you 'know' you handled that error
before and now you can assume that the other part of your 'product type' has a
certain shape but they are inherently separated types.

In Rust you get matching and function expressions (often from traits) flowing
through a tree. There is a type soundness to this: Each branch in isolation is
a statically checked part of the whole tree.

(I have personally never used an advanced IDE for Go, but I can at least
imagine that a sufficiently powerful one can catch quite a few of these cases
as well.)

------
silon42
>Most linters or IDEs will catch that you’re ignoring an error, and it will
certaintly be visible to your teammates during code review.

This is de-facto moving language specification/design to an outside project...
that linter should be built into the compiler.

(I don't disagree with the idea, but I've not done much code in Go (less than
Rust, Python, C++, c# and Java -- probably in that order)

------
merightnow
For me error handling in go was one of the biggest downsides. Using ADTs and
union types for Either is most pleasurable error handling case I've
encountered.

------
nicoburns
Handling errors as values is indeed nice. While exceptions can be convenient
in some cases, I find that the out-of-band control flow is over-complicated
and more of than not leads to poorly documented error paths that are treated
as an afterthought.

Where Go's error handling falls down is that it still doesn't force you to
handle the errors. Result sum types like Rust and Swift so much better than
anything else that I've used that I actually think that other languages
(including Go, but also Java/C#/JavaScript/etc) ought to consider adding the
feature and making it idiomatic.

------
twic
IMNERHO, the one significant misstep in checked exception based error handling
is that languages don't (AFAIK!) require call sites to be marked. If a
function is marked as throwing an exception, and it's called from another
function marked as throwing the same exception, that's enough. There's no
indication to the reader that an exception can be thrown at a particular
point, much less the the author was aware of the fact that it can!

Go's if (err != nil) { return err; } and Rust's ? both do that.

You could imagine a variant of Java where you had to write this:

    
    
      Byte readOptionalByte(InputStream in) throws IOException {
        int flag = try(in.read());
        if (flag == 0) return null;
        else return (byte) try(in.read());
      }
    

Which might be better.

Unchecked exception based error handling, of course, is irretrievably broken.

~~~
95th
Well, in Java's defense, at least you don't forget to check the error like you
could in Go.

~~~
cy_hauser
While I'm no great fan of Go's error handling, I've not found this to be an
issue. The compiler yells at you if you forget. You'd have to explicitly
ignore the error by underscoring it to "forget" it.

~~~
apta
No it doesn't:

    
    
        err := foo()
        if err != nil {...}
        err = bar() // accidentally ignored
        err = baz()
        if err != nil {...}

------
kstenerud
Error handling is one of those things that involve a lot of nuance, and that
most people try to turn into a black-and-white argument.

The fact is, there are different classes of errors that need to be handled
differently. Even the standard go runtime library operates on this principle.
Some errors are returned to the user as a return value, while others trigger
exceptions (called "panics" in go).

Trying to shoehorn all errors into a single mechanism is a fool's errand.

------
apta
It seems many people praising golang's error handling have yet to use it in
large production projects. It's absolutely horrid dealing with it.

------
LordHeini
The author seems not to be aware, that there is a "better" version of
fmt.Errorf using %w instead of %v since go 1.13.

Which allows to unwrap nested errors by their type.

Doing it the stringly way often forces one to do string compare on the errors
message. Which obviously is just bad.

In general the go error handling is plainly awful.

A common problem is, that functions always return something besides the error,
which may or may be not a null pointer or random crap.

So one is free to ignore the error and then do other stuff with the return
(like a later null check which might rely on undocumented behavior)

I really do not get it. Go has implicit interfaces but yet the most verbose
and impractical error handling ever. Implicits like Scala! Making all the "go
is explicit because it makes it easy to reason about and thus verbose error
handling" arguments invalid.

Union types are a way better way to do proper error handling as proven by
countless programming languages like rust and all the functional ones.

------
lerno
As alternative, I introduced “failables” into my programming language C3. It
has the benefits of Go error handling but none of the downsides. I wrote two
blog posts as I developed the idea:

[https://dev.to/lerno/a-new-error-handling-paradigm-
for-c3-2g...](https://dev.to/lerno/a-new-error-handling-paradigm-for-c3-2geo)

[https://dev.to/lerno/more-on-error-handling-
in-c3-3bee](https://dev.to/lerno/more-on-error-handling-in-c3-3bee)

Composing calls is much easier for one, plus there is quite a bit of syntactic
sugar to make things even smoother.

------
dariusj18
If all functions return a possible error, how do you return a value OR an
error?

~~~
learc83
You can't which is a big part of the problem. You have to trust that the
function will only return either and not both. Except in cases where a
function does return both.

A maybe type would have been a much better solution. I wish people would just
accept that go's error handling is a stopgap solution instead of pretending
it's a good (or the best) solution.

I'd bet a substantial amount that go in 10 years ends up with pattern matching
and sum types.

~~~
ribasushi
A "maybe" type is logically incompatible with "subatomic I/O". When a sized-
read() syscall results in a short read AND a raised error. The only sensible
thing to do is to return both.

I am not familiar with Rust, but looking around the docs on Read
[[https://doc.rust-lang.org/std/io/trait.Read.html#errors](https://doc.rust-
lang.org/std/io/trait.Read.html#errors)] I see:

> If an error is returned then it must be guaranteed that no bytes were read.

Could someone elaborate how a system can satisfy this guarantee, seems
impossible...?

~~~
Measter
That's an API choice. There's no technical reason at all why you couldn't
return some kind of error AND read data if you wanted, you'd just have to make
your Error type a product type.

------
Kolja
I think Zig's error handling is even better: [https://ziglang.org/#toc-A-
fresh-take-on-error-handling](https://ziglang.org/#toc-A-fresh-take-on-error-
handling)

Return values are a pair of an error value (internally basically an enum) and
a result value. Error handling is mandatory, but passing errors to the caller
from a nested function can be done by prefixing with `try`.

IIRC, Swift is similar.

------
ppeetteerr
This is a similar argument as for functional programming: sure it's great that
functional programming has pure functions, but most languages allow for pure
functions _and_ for procedural code.

Similarly, most languages (Java, Swift, etc) have generics, tuples, _and_
thrown exceptions. It's up to the user to determine whether to return a
Result<T, E>, a (T, E), or throw an exception when an error occurs.

------
empath75
There are other ways of handling errors besides exceptions.

~~~
throw_m239339
The question is, Is the author of that post aware of that at first place. Or
is he building an disingenuous argument by ignoring other forms of error
handling beside exceptions, in order to make Go "convention" look good?

Here how I think. There is a compiler? Put it to good use to help the
developer instead of wallowing in "good practices" nonsense and
"philosophies". Fact is, there is no error handling in Go (beside panics).
It's all a convention.

~~~
cameronfraser
Judging from the way the author presented the info, I think they are clearly
unaware to there being other methods of handling errors other than exceptions.

------
julik
I find it hard to reason about text which contains stylistic assumptions which
are quite handwavy, such as

> which is opaque, hard to reason about, and can encourage some lazy
> programming habits.

In the same handwaving vein to me it is not opaque and does not encourage lazy
programming habits. Having to return `err` is no different to raising an
exception except that you do not do stack unwinding, but it does force you to
write more code because the language designers have decided that dealing with
every possible error, inline, using conditionals, is way more important to
them than making the happy path of the algorithm readable as one unit of
prose.

To me there is no difference in outcome when the happy path is written out
first, and then the possible failure conditions are enumerated. It can be
accomplished with sum types, but it can also be accomplished by matching on
exception types.

The difficult part about Go's errors is that they are neither. For example, if
you want a backtrace: congratulations, you have to use `errors` and wrap every
single error you receive from your callees. You want to accumulate the causes
of an error and pass it up? Well you better hope that your callees already
have stored this metadata for you. The error is in
`net/http/internalpkgoranother`? Bad luck, as it surely won't be wrapping for
you. So some essential metadata is missing. Want to avoid ambiguous returns?
Bad luck. If you read from an `io.Reader` and it returns you no error but the
"read count" of 0, what do you do then? When you type match on the return
value of the `io.Reader.Read()` and it tells you it read N bytes but it also
gives you the EOF error - what do you handle first?

Even the basic Go idioms which are touted as great examples of composability
and interface application (with which I can agree actually) have these edge
cases of a double-meaning-return. Neither exceptions NOR sum types have this
problem, so I guess both camps are unhappy.

What does put me off though is that the Go community seems to have this style
of examining a (possibly arbitrary) Go design choice, and then to position it
as THE way to write software. Sometimes it is done by denigrating developers
who voice their stylistic disagreements with the choices designers have made
("you are not smart enough to use exceptions", "you are not smart enough to
use sum types", "you are not smart enough to use generics"), and sometimes it
is done with handwavy statements about code using other idioms being "opaque
and hard to reason about".

I would really appreciate if the Go community could be a bit more mindful of
when their praises of language designers sound to people preferring other tech
like lecturing (or scolding, if you will). For instance because despite some
very large projects using Go it might be alienating or "othering" a lot of
people who do not share the enthusiasm or would otherwise use the language –
and contribute to the ecosystem in a meaningful way – just for the sake of
getting the job done.

------
thysultan
try catch can provide the best of both worlds if your language allows
statements as values

    
    
      var value = try fib(2) catch 'error'

