
Go proposal: Leave “if err != nil” alone? - hih0
https://github.com/golang/go/issues/32825
======
mey
Go's error handling reminds me of C or PHP error handling. Check the status
code. Since everything can mess with that error condition, you have to be
careful in how you handle it, but there is nothing forcing you to handle it. I
like exceptions because handling an error is default delegated until something
wants to or can handle the error. In the case of go, if there is a layer above
your function that should process the error, you have to do that if/err in
each function that isn't going to handle it. I strongly believe in halting on
error as the default response, and unwinding until the correct layer can
proceed.

~~~
butteroverflow
>PHP

Oh no, don't even put them in the same sentence. I am no fan of Go, but its
error handling approach is beautiful compared to PHP. Every time I call an
internal function I have to go through the docs — does it return a NULL, an
int, a boolean, or something else? Does 0 signal an error condition, or is it
a valid value? Do I have to perform a strict check for NULL/false then? Or is
it -1 (see link below)? The situation is generally better with third-party
libraries though — they tend to just throw exceptions (if that matches your
definition of "better").

[https://www.php.net/manual/en/function.openssl-
verify.php](https://www.php.net/manual/en/function.openssl-verify.php)

~~~
alyandon
Rightly or wrongly, many PHP functions are thin wrappers around third-party C
libraries and they tend to return values without interpretation of the
results.

For your example of openssl_verify see
[https://linux.die.net/man/3/x509_verify_cert](https://linux.die.net/man/3/x509_verify_cert)

It can certainly be confusing but at least it is documented.

~~~
PhasmaFelis
I'm curious who it was who first started implementing those wrappers and
thought "Yeah, this is good enough."

~~~
mkr-hn
The late '90s and early '00s were a strange time. It had plenty of shotguns
aimed at feet and no one to suggest a better approach. At least no one with
enough reach.

Also [1]:

>> _" I don't know how to stop it, there was never any intent to write a
programming language [...] I have absolutely no idea how to write a
programming language, I just kept adding the next logical step on the way"_

I assume he's better at it 16 years later.

[1]
[https://en.wikipedia.org/wiki/PHP#History](https://en.wikipedia.org/wiki/PHP#History)

------
madrox
I've only just started using Go (coming from Python), and I find the existing
error pattern cumbersome. It often gets in the way of understanding the
function at hand. I think the intention behind the existing pattern is to not
over-emphasize the happy path and ensure error conditions are dealt with so
programs don't crash, but 99% of the time all I see error handling do is just
print the error and then return.

There has to be a better way.

~~~
marcrosoft
> all I see error handling do is just print the error and then return.

Yeah, that is a problem. At least they had to manually type it out and (maybe)
think about how to handle the error during that time. To streamline this is to
further exacerbate the issue. They will do a try/catch try/defer
`log.Println`.

With Python try/catch allows for ultimate laziness. It allows for 1,000 line
try/catch blocks that can hide/capture errors that bubble up from anywhere. Of
course this is bad development but it's possible. Currently with Go it is
impossible to forget about error handling. You have to consciously make a
choice on what to do with it. Even if that choice is to log/print it out.

~~~
msbarnett
> Currently with Go it is impossible to forget about error handling.

You’re describing some other language that forces you to deal with errors,
like Haskell or Rust.

A bare

    
    
        io.WriteString(w, “ok”)
    

will happily compile despite returning a completely unhandled error.

~~~
xmprt
Meanwhile code like

    
    
        a, b := someFunc()
        fmt.Println(b)
    

throws a compiler error like it's the end of the world if you define a
variable that isn't used.

~~~
pstuart
If you can type "a, b :=", you can type "_, b :=".

It's not a big deal.

~~~
saghm
When I wrote Go at work, this would mostly be annoying when interactively
writing and running code; if I want to comment out the only line below that
uses `a` and run it again, now I have to go back and change the declaration
also. Once I want to to run that line again, I can't just uncomment it; I have
to go back and declare it again. I would have to do stuff like this fairly
often when trying to debug failed tests, so despite the fact that I'd never
want to _commit_ code with an unused variable, I certainly would like to be
able to _run_ it when debugging. I feel like a warning is definitely better
than a hard error here; it's pretty easy in most languages with warnings to
set up CI to reject unused variables, which ensures that your code in
production won't have any unused variables.

~~~
pstuart
Understood. I think the problem is that once options like that are given they
become abused and eventually institutionalized.

A quick work around for that example would be to a line: `b = b`.

Go vet complains but it builds and runs.

------
jammycakes
The problem with explicit error handling is that it's all too easy to get it
wrong (by forgetting to check the return value) and when it does go wrong, it
goes wrong silently, introducing a risk of leaving you with corrupt data. In
production.

The beauty of exceptions, on the other hand, is that the default option is the
safe one. Sure, forgetting to add error handling may leave you presenting a
user with a stack trace, but at least you're not billing them for something
that never gets delivered.

~~~
stcredzero
_The problem with explicit error handling is that it 's all too easy to get it
wrong (by forgetting to check the return value) and when it does go wrong, it
goes wrong silently, introducing a risk of leaving you with corrupt data. In
production._

The problem with exception error handling is that it's all too easy to get it
wrong (by forgetting to complete the handle code) and when it does go wrong,
it goes wrong silently, introducing a risk of leaving you with corrupt data.
In production.

In either case, error handling needs to be code reviewed. The best thing to
do, is to make the right thing the easiest, minimal friction thing to do.
Unfortunately, getting off the "happy path" is often a messy business. My
suggestion is to explicitly implement a standard "developer scaffold" to be
used when filling in the error handling during development, with penalties for
not using it. This makes it easier to find where error handling needs to be
fully fleshed out.

 _The beauty of exceptions, on the other hand, is that the default option is
the safe one. Sure, forgetting to add error handling may leave you presenting
a user with a stack trace, but at least you 're not billing them for something
that never gets delivered._

The entire reason why Unit Testing and Test First made it into Extreme
Programming and Agile methods, is that it was way too easy for end-users to
see error notifiers and stack traces in production in Smalltalk.

~~~
apta
> The problem with exception error handling is that it's all too easy to get
> it wrong (by forgetting to complete the handle code) and when it does go
> wrong, it goes wrong silently, introducing a risk of leaving you with
> corrupt data. In production.

It's much more difficult to "forget completing the handling code" than it is
to overwrite a golang error value and not handle it (which does happen in code
bases). The default behavior of unhandled exceptions is to bubble up, unlike
golang errors that can get silently dropped quite easily, and I've seen this
is in large code bases. Unless you're writing

    
    
      try { ... } catch (..) { /* do nothing */ }
    

in which case you explicitly opt into doing nothing, and for which there are
linters that catch this sort of behavior automatically.

On the other hand, even the simple

    
    
        func main() {
            fmt.Println("")
        }
    

doesn't handle errors.

~~~
stcredzero
_in which case you explicitly opt into doing nothing, and for which there are
linters that catch this sort of behavior automatically._

There are linters for golang. No reason why those sorts of tools and community
norms shouldn't squash those sorts of behaviors. (As has happened for race
conditions in golang!)

~~~
apta
I used such linters, and they fail at certain things. The `fmt.Println("")`
example still holds. Another example:

    
    
      a, err := foo()
      b, err := bar()
      c, err := baz()
    

err doesn't get handled in the first two cases, and the linter doesn't
complain.

And race conditions in golang are very much possible, and cause issues in
prod.

~~~
stcredzero
_I used such linters, and they fail at certain things._

Of course. They're just a tool.

For the amount of concurrency done, golang is doing pretty good. I know of no
other programming community which has such community standards on using race
condition checking to the same extent as linting.

~~~
apta
> Of course. They're just a tool.

Which is why it is strictly superior to use a language feature which does not
have this issue to begin with (e.g. exceptions or actual compiler enforced
error handling like Rust).

> I know of no other programming community which has such community standards
> on using race condition checking

I assume you're referring to the golang race detector. It's better than
nothing, but then again, it's a tool and is not guaranteed to find all issues
that may arise. Compare (again) with Rust, or with languages with proper
immutable data structures like Scala and Java (e.g.
[https://immutables.github.io](https://immutables.github.io)), not to mention
[https://openjdk.java.net/jeps/8208520](https://openjdk.java.net/jeps/8208520)
or
[https://clang.llvm.org/docs/ThreadSanitizer.html](https://clang.llvm.org/docs/ThreadSanitizer.html)

~~~
stcredzero
_actual compiler enforced error handling like Rust_

Or Java?

 _Compare (again) with Rust, or with languages with proper immutable data
structures like Scala and Java_

To paraphrase you from earlier: As far as I know, race conditions happen and
leak out into production with Scala and Java.

The lesson of golang, and really the lesson of programming in the large for
the past 30 years, is that it's not enough to have mathematical or
methodological power in guaranteeing correctness in a program. If that were
the case, formal methods would have won decades ago, and we'd all be using
such environments. It's not about having the most rigorous widget. It's
getting programmers to do the right thing, month after month, year after year,
in large numbers, across different management teams. Arguing the strength of
constructs within programming languages is just an idle meta-anatomic
measurement exercise.

That said, I like lots of things about Java, Scala, and Rust. I just happen to
really like golang for a different set of reasons. Golang is
concurrency+programming-in-the-large-human-factors "Blub," in a way that
refutes PG's essay about Blub.

~~~
apta
> Or Java?

Yes - that was implied in the same sentence where I wrote "(e.g. exceptions or
actual compiler enforced error handling like Rust)." :-)

> As far as I know, race conditions happen and leak out into production with
> Scala and Java.

They do. But there are more techniques in those languages (such as immutable
collections, as their type systems are actually able to model and implement
them) to mitigate the issue.

> and really the lesson of programming in the large for the past 30 years, is
> that it's not enough to have mathematical or methodological power in
> guaranteeing correctness in a program.

I agree there. I actually don't abide by the ideas that using a language like
Haskell or Idris or what have you will automagically grant you superior or
error-free software. I actually like what talks like these have to say:

* [https://www.youtube.com/watch?v=dWdy_AngDp0](https://www.youtube.com/watch?v=dWdy_AngDp0)

* [https://www.youtube.com/watch?v=449j7oKQVkc](https://www.youtube.com/watch?v=449j7oKQVkc)

However, that does not mean we disregard established practices, only to try to
reinvent them in a bad way as we see golang trying to do (no generics - even
though many other languages implemented them "correctly", bad error handling,
no sum types, null pointers, and so on). They purposely disregarded these
works without good arguments at all, and now they're struggling to find
workarounds, or continue to dismiss them as non-issues, even though they
clearly are.

The way I see it, the moment Java gets fibers and value types implemented, and
GraalVM's native compilation is mature enough for use, golang's appeal as a
devops langauge where it ended up (however low it is currently - as it's
mainly over hyped) becomes even less. C#/.NET is the other alternative making
very good progress, and they already have async, value types, and NGEN (though
I don't know how mature the latter is for production).

> programming-in-the-large-human-factors

This seems to continually get mentioned without anything to back it up. Sorry,
but from what I've seen, Java and C# are strictly superior for "programming in
the large". Things like "fast compile times" aren't even a factor in front of
Java and C# with their incremental compilation. And the fact that the language
is "simple" just means that you will end up with more verbose and complex
code, with more places for things to go wrong, and yes I've seen it in larger
golang code bases.

------
djsumdog
I've found I really like error handling with monads, in languages like Scala.
You can have the same boilerplate where you match an Option type on if it is a
Some/None or Try type with Success/Error, but you can also chain them together
in flatmaps and have one location to return an error.

Such things probably wouldn't work in systems languages like Go. I'm surprised
we haven't seen more push for functional system/embedded languages.

~~~
andrewjf
I mean, Rust has exactly those things (Option, Result types), they are
iterable, and is a systems language.

------
empath75
I sort of like how brain dead go is, now. It’s not pretty but it’s obvious
what the code is doing for the most part.

~~~
joobus
Agreed. I am not a fan of the Go 2 proposals, generally. I’d rather just use
Rust if Go is so intent on reimplementing (poorly) everything missing from Go
that Rust already has.

------
frou_dh
The answer to, paraphrasing, "Did you consider doing nothing?" is obviously
"Duh, yes".

So I don't see this proposal accomplishing anything beyond generating heat on
commenter keyboards.

------
kstenerud
I would have liked to see "or return blahblah" type syntax to make it easier
on the eyes:

    
    
        f, err := os.Open(filename) or return 0, err
    

Actually, I'll just open a proposal:
[https://github.com/golang/go/issues/32848](https://github.com/golang/go/issues/32848)

------
kyrra
The thing is... the "try" proposal doesn't stop people from using the existing
error handling. To me, 'try' is there to simplify the simple cases where you
don't need to do anything special except return the 'err' from a function
call.

There are many ways error handling could be changed that would make the system
more complex (such as the "expect" or "catch" proposals he links to). 'try' is
to handle the super simple, repetitive case.

One more downside I could see is that people will just start using 'try'
without thinking about how they should really handle an error, but I feel like
that already happens today, so 'try' should just simplify code.

------
arendtio
What I find most interesting about the whole 'try' discussion: There is one
thing that makes the try-function special: It can trigger a return for the
calling function.

But instead of finding a general way how we can write functions that can do
this, we want to add a function that looks like all the other functions but
doesn't work like them.

So even if I like what we are trying to achieve here, it feels wrong to me.

~~~
cesarb
> we want to add a function that looks like all the other functions but
> doesn't work like them.

I like how Rust does it: all macros and special built-ins like these have
names ending with an exclamation point. So when you see for instance
"try!(something())", you know that this "try!()" is not a normal function
call.

------
tptacek
I don't understand why this is on the front page. It's a random Github Issues
thread. Unless I'm missing someone, the only Go team member participating is
the person telling people not to use Github Issues threads to discuss language
changes.

~~~
alanbernstein
Because it's relevant to the Go 2 proposal, and someone wanted visibility?

------
jrockway
I am also okay with checking each error. I think errors.Wrap makes for very
readable log messages, and still lets you handle different errors differently.

The complaint about go programmers silently ignoring errors seems off to me.
If you have a function declared like "func foo() (int, error)" and then try to
silently ignore the error like "x := foo()", the program won't compile. You
have to go in and say "x, _ := foo()" which does look pretty weird. If you do
that, you've ignored the error. It's no different than "try { foo() }
catch(Exception e) { }" which I guarantee you plenty of programmers do. The
resulting faults are their problem; someone tried to help them but they shot
themselves in the foot.

The real problem is the special case of functions where you want to ignore the
value. You can then ignore the error freely. (A good example is fmt.Printf. It
returns (n, err). Do you ever look at either? Did you even know that it
returns something?)

If anything, that's the issue I'd want to fix. Calling fmt.Printf in a "void
context" should say "assignment mismatch: 0 variables but Printf returns 2
values". Then the compiler completely prevents you from ignoring errors (and
useful results) without making a conscious effort to do so.

As for errors themselves, generally I quite like the detail produced by a
chain of errors.Wrap. It is more concise and customizable than a full stack
trace, but still lets you track down problems. The annoying part is that you
can't translate these errors into a less-detailed error for external consumers
easily. For example, you might have a chain like "get foo: authenticate:
lookup session in database: context deadline exceeded". You want your gRPC
service to then return codes.DeadlineExceeded so the client can retry. But you
don't REALLY have a good way to convey that programmatically, resulting in
ugly code like `if strings.HasSuffix(err.Error(), "deadline exceeded") {
return nil, status.Error(codes.DeadlineExceeded, ...) }` which I think is
probably prone to error and annoying to write.

Checking for errors, though... it doesn't annoy me at all. In 46k lines of
code I've written in the last year, "err != nil" is on 1500 of them (500 in
tests), and I am super super paranoid about every possible error. It just
isn't that big of a deal. "if ...; err != nil" is something you'll probably
type if you write go. I don't think it's a plus or a negative. It gets the job
done, it's easy to understand, and it empowers the programmer to do the right
thing when the program fails. The rest is up to you.

------
icholy
Proposal: don't change anything so we can keep complaining about the same
things forever.

------
iainmerrick
Why don't they at least allow "if err" rather than "if err != nil", i.e. allow
error values to be truth-tested?

I don't think I've seen that suggested anywhere, which is surprising to me. I
guess it's fear of scope creep, of allowing syntactic shortcuts that might be
ambiguous or dangerous in other circumstances? But I would have thought it
could be restricted to error types.

~~~
networkimprov
This proposal
[https://github.com/golang/go/issues/32611](https://github.com/golang/go/issues/32611)

suggests:

    
    
       on err, <single_statement>

~~~
iainmerrick
That’s not the same.

------
danielinoa
Swift has an elegant solution to this called Optional Binding:
[https://docs.swift.org/swift-
book/LanguageGuide/TheBasics.ht...](https://docs.swift.org/swift-
book/LanguageGuide/TheBasics.html#ID330) Go would benefit from something like
this.

------
wst_
The practice shows that people forget to handle errors quite often and if you
are using "golint" it won't even tell you. VSCode doesn't issue a warning,
either. I see it all the time in the code that runs on production for few
years, now. Thankfully these are not critical issues and, thankfully, haven't
manifested yet. But those people are good engineers and seasoned Go
developers. If they miss it sometimes, how often it happens in code written by
juniors? We are just people after all. Properly designed language should have
safety guards everywhere, especially when it comes to errors handling.

------
40acres
I've never used Go, but why not treat err like a Boolean? One of my favorite
patterns in Python is a simple "if item: do x" where item can be a sequence, a
string, or even an integer, where certain values are considered False and
certain values are considered True.

~~~
frou_dh
In general, they deliberately shunned implicit type conversions (in this case,
error/interface → bool. Also notably between numerics). Probably due to being
fed up from many years exposure to C's static-but-weakly-typed nature.

------
notus
They are adding generics so why not do something like Rust does
([https://learning-
rust.github.io/docs/e3.option_and_result.ht...](https://learning-
rust.github.io/docs/e3.option_and_result.html))

~~~
monocasa
That would take adding enums, which they aren't doing.

~~~
akavi
For anyone else who was confused: "enums" is what Rust calls sum types.

~~~
monocasa
Well, and they've been called that in other languages for decades too. The
naming derives from the ML family into Rust.

------
lazyjones
IMHO, they should stop tacking on all those afterthought warts that add a lot
of complexity to the language for very little benefit and instead add a
simple, more powerful way to let the users implement their own shortcuts, i.e.
a macro system.

------
lgunsch
I find two of the solutions, the `try` and `catch` to be pretty explicit as
well, but a lot easier to read, less gratuitous context. The `expect` I find
confusing, and hard to follow. I think it would be confusing enough to cause a
lot of bugs.

------
habitue
Stockholm syndrome

------
CalChris
Mods: the title is wrong. It should be

    
    
      Proposal: leave "if err != nil" alone? (Go)

~~~
danskeren
Not sure what happened, I’m guessing HN doesn’t allow “!” in the title.. I
also can’t recall ever seeing a title with an exclamation mark.

