
Declined Proposal: A built-in Go error check function, “try” - robfig
https://github.com/golang/go/issues/32437#issuecomment-512035919
======
wybiral
This hits at something fundamental about Go, which is what I like the most
about it...

It's a language intended to have few primitives with an emphasis on code being
transparent and errors being values, requiring you to think about what they
might be at each point as you're forced to carry them up the chain.

Do I particularly _like_ managing errors that way? No, but I do think that it
improves the transparency and quality of a lot of Go projects.

The same goes with the Go ecosystem and tools. You might not like how gofmt
forces your code or the documentation conventions used by golint but the fact
that we all use the same convention and documentation is awesome and it's what
allows things like godoc.org. I think the proverb is "gofmt's style is no
one's favorite, yet gofmt is everyone's favorite".

And things like the lack of abstractions and generics are what create a
community that's less reliant on dependencies. "A little copying is better
than a little dependency" and you can see it in stark contrast to something
like the JS community with its require('left-pad') NPM ecosystem.

So, yeah, I like that they didn't fragment the community around something as
arbitrary as this. And I get that some people won't like it, just as they
don't like the lack of generics, but there is strength in some of these
approaches that isn't immediately obvious.

~~~
KirinDave
> Do I particularly like managing errors that way? No, but I do think that it
> improves the transparency and quality of a lot of Go projects.

So long as we can all agree that it feels super bad, I guess this is fine. But
it does sort of mean that Golang approaches the Java world back with checked
exceptions where principle trumped ergonomics.

That lead to a world where folks felt "forced" to use Java, and that's a
sticky label that's difficult to remove. I think the Golang community is going
to find itself growing more slowly as folks increasingly realize other options
offer similar benefits without the bad ergonomics.

~~~
NateDad
Literally the first non-trivial code I wrote in go (running a bunch of
goroutines to download a ton of files from a website in parallel)... I knew
exactly where and how things could fail and where things were failing just by
looking at the code. Coming from C++ and C# and Python, there was no
comparison. I had never been so confident in the code I'd written, even though
I was a newbie at Go and a veteran of the other languages.

~~~
KirinDave
I guess then I have to ask, why would try() make that worse?

Because I can't stand Golang error handling. It's repetitive, it's error
prone, and other language features interact with it so that when you make a
mistake it can be as hard as a double free to track down where the erroneous
default value was introduced.

On the other hand, using Rust, Ocaml, F# or Haskell I understand how my code
composed and I can be confident I can just use an error handling strategy. The
only complexities appear, as with everyone else, when we have asynchronous
code.

So I don't mean to disagree with your feelings, but they're sure not mine and
they're part of why I don't use Golang. I was excited about try() because it
at least addressed the most tedious part.

~~~
NateDad
Try makes it worse because it is so easy to miss when reading the code.
Because it encourages nesting function calls, which is harder for a human to
parse than separate statements across lines. Because it means you can exit the
current function from the middle of a line of code, and what runs before or
doesn't run before is based on order of operations rather than requiring the
exit to be a statement on its own line whose order cannot be misunderstood.
Because it discourages giving more information with an error, so instead of
"failed to open config file: EOF", you just get the EOF.

Go's error handling isn't any more error prone that writing if statements for
all the rest of your code. if err != nil is no different than if age < 18\.
Either one is a branch in the code. I can literally count on one hand the
number of times in 6 years of full time go development that I've seen people
miss writing the if err != nil.

Being explicit is good. Spreading out the logic is good. Cramming a lot of
logic into one line is bad.... and that's the sole purpose of try.

Maybe there's another way to make error handling better in Go. I'm not averse
to looking into that. But try wasn't it.

You're talking about writing if err != nil being tedious, but what about
matching Results from Rust, isn't that tedious? What about writing proper
catch blocks in java or C++ or python, isn't that tedious? It's all just
logic.

~~~
tsimionescu
The difference with catch blocks in Java, C++ or Python is that you only need
to write them when you actually have something g meaningful to do.

If you only need to propagate the error or cleanup resources then propagate
the error, then all you would write is... Nothing. And cleanup+propagation is
by far the most common error handling strategy. In Java and Python exceptions
even add context for you automatically to help track down what happened.

~~~
sagichmal
One foundational principle of Go is that the sad path is at least as
important, and maybe more important, than the happy path. The best Go
programmers I know write the sad path of their programs first, and then
backfill the happy-path logic. So:

> you only need to write [error checking] when you actually have something
> meaningful to do.

Although it's the subject of a lot of ridicule, `if err != nil { return err }`
is actually bad Go code, and not often written by good Go programmers. Errors
in Go are, at a minimum, annotated with contextual information before being
returned. Frequently, they are programmed-with in other, more sophisticated
ways, depending on the domain of the program.

Shifting your mindset to understand errors as something significantly more
important than the off-gassing of your program's execution is worthwhile in
general, and, in Go, fundamental.

~~~
VMG
> Errors in Go are, at a minimum, annotated with contextual information before
> being returned.

What surprised me when I last wrote Go was that there was no out-of-the-box
solution to adding a stack trace to the error.

~~~
Cthulhu_
Does anyone know if this was a conscious decision? I mean IIRC in Java you're
generally discouraged from throwing errors for control flow because creating
the stack trace is a relatively heavy process. In Go this is of less concern
and returning an error is pretty normal for control flow (as in errors are
expected, not exceptional), and you shouldn't have to worry that an error path
would be 100x as expensive as a normal flow because a stack trace is being
generated.

~~~
EmpirePhoenix
Java allows since ~a decade time to omit the generation of stacktraces, for
exactly such cases

------
jph
Kudos to the Go team for their process on this. IMHO it's worth reading Russ
Cox's explanation of the problem area, including examples, and comparisons to
other languages e.g. Rust and Swift.

[https://go.googlesource.com/proposal/+/master/design/go2draf...](https://go.googlesource.com/proposal/+/master/design/go2draft-
error-handling-overview.md)

~~~
0815test
"But Rust has no equivalent of handle: the convenience of the ? operator comes
with the likely omission of proper handling." what's that supposed to mean?
The ? operator just bails out if an Error result is returned from the called
function, and forwards that Error to the caller. Cleanup is performed
implicitly by drop implementations (destructors) using the RAII pattern ala
C++.

~~~
MaulingMonkey
In the draft design, they give an example of special-case cleanup that would
only execute _only when an error occurs_ , not on the success path.

You can emulate this with a boolean flag in your RAII types in Rust or C++,
that's set or cleared immediately before a successful return, and then doing
conditional logic in your Drop/dtor. Or you could do a std::mem::forget before
successful returns. But I guess they think this is an important enough case to
dedicate syntax to it for ergonomic reasons, which Rust doesn't have - which
is what I think they're getting at.

~~~
0815test
> execute _only when an error occurs_

You can use the match construct for that, and the Result<T,E> type comes with
some utility methods that make it easier to clarify your desired semantics in
many cases. The page complains that the "match" syntax is clunky, but I'm not
_that_ sure how 'handle' is supposed to be better.

~~~
MaulingMonkey
As Arnavion points out, handle handles multiple error cases. That said, I'm
not convinced it's better _in practice_. For some comparison points - here's
how I'd write the Go CopyFile in Rust:

[https://play.rust-
lang.org/?version=stable&mode=debug&editio...](https://play.rust-
lang.org/?version=stable&mode=debug&edition=2018&gist=c833de2b2d697abeed59cd59cec8fd1b)

Or, if we want to keep a more 1:1 direct mapping to the Go code:

[https://play.rust-
lang.org/?version=stable&mode=debug&editio...](https://play.rust-
lang.org/?version=stable&mode=debug&edition=2018&gist=c833de2b2d697abeed59cd59cec8fd1b)

Caveats with the "1:1" mapping:

1) In a real rust codebase you'd probably simply forward std::io::Error
instead of converting it into a string like I have here, or give it a better
error struct/enum type. I've tried to mimic the Go code here, not fully
convert to Rust idioms.

2) You could get rid of the .as_ref() spam by just using &str or &Path to be
closer to the Go code, but I'd rather stick at least that close to
std::fs::copy's file signature.

3) All explicit close operations are dropped as unnecessary vs the Go code. I
guess I could've used std::mem::drop to be more explicit?

4) TempPath is obvious overkill for the single remaining error point

5) In temp-file heavy code you'd probably wrap TempPath + File into TempFile.
keep could return the interior File as well.

~~~
Arnavion
>1) In a real rust codebase you'd probably simply forward std::io::Error
instead of converting it into a string like I have here, or give it a better
error struct/enum type.

In all the real code bases I've worked on, there are multiple disparate types
of errors that nevertheless have the same context.

Example: A function that takes in a path and parses a config file at that path
fails if it can't open the file or if the file is malformed. The file can be
malformed because indentation is wrong, because there's a string where there
should be an integer, or because a required field is missing. All of these are
different error types.

So a single `std::io::Error` is not possible, and erasing them into a `Box<dyn
Error>` or wrapping them in a custom (context-containing) type nevertheless
requires writing a `.map_err` per each Result value.

~~~
MaulingMonkey
`?` uses the `From` trait which means you often don't need `.map_err`.
Ignoring crates like error_chain, even the stdlib comes with a From
implementation for `Box<dyn Error>` - as long as your error types implement
std::error::Error, you shouldn't need an explicit .map_err to box them:

[https://play.rust-
lang.org/?version=stable&mode=debug&editio...](https://play.rust-
lang.org/?version=stable&mode=debug&edition=2018&gist=f8aaf49fee7632058db95e9bc72b31a2)

EDIT: That said, adding extra context will often require map_err or similar.
But merely type erasing / combining error sources shouldn't need it, unless
I'm missing something. (Most of my Rust use so far has been on toy
codebases...)

~~~
Arnavion
>`?` uses the `From` trait which means you often don't need `.map_err`.

1\. `From` is a global solution to a local problem. All `std::io::Error` must
necessarily be converted to the same enum variant regardless of what caused
them. Failing to open a file and failing to write to a network socket will
create the same variant.

2\. `From` does not have access to context anyway. A `From<std::io::Error>` is
not going to know that the error was hit specifically when "parsing the
/etc/foo.conf file", and a `From<std::string::ParseError>` is not going to
know the error was hit when "parsing the bar field of the /etc/foo.conf file
because it is set to `baz` which is not an integer".

>as long as your error types implement std::error::Error, you shouldn't need
an explicit .map_err to box them:

This only works when you want your function to return `Box<dyn Error>` itself
without any additional context. The conversation was about needing to add
context like in the golang example.

------
pcwalton
I suspected this was coming, and it's unfortunate. If Go had implemented try,
then in a year everyone would be happy using it and the controversy would have
died down. I've seen this happen before.

Unfortunately, the community that has sprung up around Go is more or less
opposed to new language features on principle.

~~~
gridlockd
The controversy may have died down, but the impact on code written would have
been permanent.

There's a lot of value to there being "one way to do things", even when it's
not the best way from any particular point of view.

Go holds this principle higher than most other languages and I think that
should be either embraced, or one should look elsewhere - and I'm saying that
as someone who "looked elsewhere".

~~~
didibus
Agreed, no one is forced to use Go. I think it is refreshing to see a language
like Go. You don't want all languages to asymptotically approach each other in
features. We need diversity in our languages, that way you have flavors to
choose from.

~~~
erik_seaberg
> no one is forced to use Go.

I suspect some people are. Languages have network effects, which get stronger
when FFI is uncommon. My team and codebase mostly keep me on Java even though
I find Kotlin clearer in _every way_ and I'd love to get better with Scala and
Haskell. If for some reason my team switched to something like Go or VB I'd
have to find another job or else put up with it.

------
unixsheikh
This is great news.

I really like how errors are handled in Go and I hated this proposal.

The current implementation forces you to constantly think about errors at each
single point and it is extremely good at standing out. This is something very
valuable, not something that requires or needs to be hidden behind syntactic
sugar. It improves the readability of the code and the quality of the
software.

If it ain't broke, don't fix it.

~~~
pcwalton
Go does not force you to think about errors at every single point. If a
function returns only an error (such as, for example, os.Mkdir), the language
will happily let you drop the error on the floor.

~~~
matthewbauer
This is underappreciated! I suppose you are less likely to care about errors
if you aren't getting values from a function, but not always. I wonder if
there is a proposal to force this case to be handled, like Haskell's -Wunused-
do-bind?

~~~
pcwalton
There's go vet. But it's inconsistent with the Go warnings-are-errors
philosophy. Ignoring the return value of os.Mkdir() is far more likely to be a
bug than an unused import is.

------
curtis
A lot of Go programmers didn't like this proposal at all. I'd like to think
this is just because they didn't think it was good enough. However, it seems
that many, many Go programmers didn't like it because they think Go error
handling is just fine the way it is.

~~~
guessmyname
I am one of the Go programmers who didn’t like this proposal.

I also spent a significant amount of time discussing with many people,
including the Go team, and I am glad the proposal was declined, not because I
like “if err != nil {…}” but because the proposal to add “try()” was not
solving a good problem. Many, and I would say, every Go programmer wants
better error handling, but “try()” was not it.

I hope the Go team keeps exploring other ideas to hopefully one day have a
better error handler.

~~~
crimsonalucard
The maybe monad is the best solution for this.

Usually to support this the language needs Enum support and a proper type
system neither of which golang has. So I'm ok with the developers just baking
in syntax for an error monad with specific sugar for extracting the value or
handling an error.

~~~
millstone
Why is a monad the “best” solution? Best according to what criteria?

Special purpose syntax can buy you a lot more. For example Swift’s try makes
it obvious which statements contain error handling without burdening each
expression.

~~~
crimsonalucard
Ok let me tell you the criteria. There are two.

First: The monad allows for composition of functions. Returning two values
does not. It breaks the flow of a function pipeline and forces you to handle
every error in the same way.

Second: Extracting the value via pattern matching guarantees that the error
will either be handled or used correctly. This is a way to 100% guarantee that
there are No runtime errors. That's right. Using the error monad with pattern
matching makes it so that there is zero room for runtime errors.

Why create a language that has runtime errors? Create a language that forces
you to handle all possible runtime errors before it even compiles.

According the criteria of safety, zero runtime errors, and expressivity via
composition the monad is the Best solution. There is literally no other way of
error handling that I know of that can catch runtime errors at compile time.

I know you tried to flip my statement on it's head by using the term
"criteria" as if there are many many different criteria for "best." And you
are right, programming is an opinionated thing. However, ZERO runtime errors
is a too powerful of a feature to assign to a specific criteria. Such a
feature is so powerful, it should be part of EVERY criteria.

If you don't understand completely what I mean by "composition" or how pattern
matching and a maybe monad can guarantee a runtime error will NEVER occur, ask
me to elucidate, I'm happy to clarify.

~~~
mcintyre1994
I'm interested in the zero runtime errors piece - how would a language with
the maybe monad handle an out-of-memory error at runtime?

~~~
crimsonalucard
There is a way of which (to my knowledge) is not implemented in any
technology.

Presuming you know how the maybe monad (or similar named monads) handles
errors. A MaybeOutOfMemoryError works in a similar way in the sense that any
attempt to use the value meaningfully will force you to handle the error.

    
    
       data MaybeOutOfMemoryError a = OutOfMemoryError | Some a 
    
       ioFunction :: MaybeOutOfMemoryError a -> IO
    
       ioFunction OutOfMemoryError = println "ERROR"
       ioFunction Some _ = println "No ERROR"
    

exhaustive pattern matching with the error flag enabled makes it so that if
you forget the OutOfMemoryError case an error will occur during compile time.

Any function that can potentially trigger a memory error should be forced to
be typed like this. However in programming, ALL functions can potentially do
this, and side effects outside of the scope of the function (total memory
available) will trigger it meaning that the only way to do this effectively is
to make ALL functions typed this way. Which is inconvenient to say the least.

That being said you can just type the main function as something that can only
accept this monadic value forcing users to wrap this handling in a top level
function:

    
    
       myWrittenProgram :: MaybeOutOfMemoryError a
        -- not defined in this example
    
        -- main is forced to be typed this way just like how in haskell it is forced to be typed IO ()
       main :: MaybeOutOfMemoryError a -> IO ()
       main Some a = printLn a
       main OutOfMemoryError = printLn "Error out of memory"
    

Obviously the above has some issues with the rest of haskell syntax and the
way things are done, but I'm just showing you a way where it is possible to
make sure that a memory runtime error does not happen (that is an unhandled
runtime error).

That being said unless you know haskell or know what a maybe monad is, you
wouldn't be able to fully comprehend what I said. Exception monads exist in
haskell, but nothing is forcing the main function to be typed this way.

------
rbtprograms
Good on the Go Team listening to the community, I definitely saw more people
against this feature than for it.

I hope they take another stab at improving error handling. I like Go a lot and
do think error handling is one place it could use improvements.

~~~
AnimalMuppet
I don't understand why anyone would be (strongly) against this proposal. If I
understand it correctly, it's just a shortcut. That is, the old (existing) way
would still work. If anyone didn't like "try", well, don't write your code
that way.

It would also appear to be amenable to an automated tool that would convert to
or from try-style.

Given that, why would people have strong feelings against? Because they think
go needs something better, and if this passes, they'll never get it?

~~~
013a
This is the prime example of what makes Go different than most other
languages. They don't put things in attached with the argument "if you don't
like it, you don't have to use it." Go is a language designed to be written
and read by teams. You don't like try. Bob does. Bob writes code that uses it.
You still have to read it.

~~~
stockkid
> They don't put things in attached with the argument "if you don't like it,
> you don't have to use it."

I agree with this. I think many Go users like Go because of its simplicity
that prevents unnecessary bikeshedding so prevalent in other languages.

------
minieggs
Perfect timing. Started mucking around with the Go compiler recently to
implement my own error handling.

[https://pbs.twimg.com/media/D_ojiEqUYAAhmrH.png](https://pbs.twimg.com/media/D_ojiEqUYAAhmrH.png)

edit: s/%s/%d/ but you get it.

------
poweroftrue
Just try writing three Go programs with error handling, then, try other
languages!

I was pissed at first. However, now I cannot code in any language without
overusing try and being scared of each line.

Using Go's error handling is actually making your code smarter, I mean, you
don't want your code to break with a weird message because of something
stupid.

The simplest example is adding a default-path whenever I'm reading a file,
Go's error handling reminds me of what to do if this file doesn't exist! Or
cannot read etc.

Don't take my word for it, Go ;) try it.

~~~
dnautics
try errors in elixir:

    
    
       with {:ok, val1} <- happy_path1(val0),
            {:ok, val2} <- happy_path2(val0, val1),
            {:ok, val3} <- happy_path3(some_val) do
         function_might_crash_let_it_crash!(some_val)
         happy_result()
       else
         {:error, :enoent} -> handle_notfound_error()
         {:error, :eperm} -> report_permission_error()
         _ -> raise("don't worry this process is supervised, let it crash!")
       end
    

low cyclomatic complexity makes for a nice user experience, and you learn the
philosophy of "if it doesn't work, just turn it off and on again". Why be
scared? Just let it go. The VM has your back.

~~~
chewxy
What's the equivalent in elixir for something like this?

    
    
        func do(a Param) (res Result, err error) {
            var aPrime T
            if aPrime, err = getResource(a); err != nil && err == RecoverableError {
                aPrime = definitelyNoErrorAlternativeGetResource(a)
            }
            var aPrimePrime T
            ... // many more steps
            return doSomething(aPrimePrimePrime)
        }

~~~
arthurbrown
What happens with this code when err isn't recoverable?

you can manage it a few ways in Elixir, struct matching is one:

    
    
      def perform(param) do
        res =
          case get_resource(param) do
            {:ok, val} -> val
            {:error, %RecoverableError{alternative_value: val}} -> val
          end
    
        do_more_things(res)
      end

------
vegancap
Quite rightly, too! It's annoying for beginners, but you quickly see the
utility in the if err != nil {} approach, or 'sad path' approach after a few
years of using Go in production. You learn to love it! To be honest, there's
very little I'd add to the Go language, if anything. It's very unique that
most Go developers share that view of a language. With Javascript, for
example, I can't wait for 'new stuff', I think that's sometimes symptomatic of
a broader dissatisfaction.

~~~
billylindeman
Agreed. When I first started writing go I thought "Wtf is this caveman
language"

And now years later I couldn't imagine going back to using exceptions. The
non-linearity of exceptions creates such a cognitive load of picking up
unfamiliar code, and this is where go really shines. I can dive into almost
any go codebase and get a lay of the land very quickly.

On another note, I've been enjoying rust's approach with Result<T,E> / the ?
operator. Same principle with less visual bloat.

~~~
ngrilly
> On another note, I've been enjoying rust's approach with Result<T,E> / the ?
> operator.

For the record, the `try` proposal which was just declined, is almost
equivalent to the `try!` macro in Rust, which was the "testbed" for the `?`
operator.

------
meddlepal
It's rather disappointing that Go has managed to bungle error handling so
badly in a language only a handful of years old.

~~~
akvadrako
Probably something about ideology overriding practicality.

Personally I find go error handling okay but that’s because I exclusively use
panic().

~~~
codr7
Just don't let any true believers near it or they'll tear you a new one.

I've published plenty of Go code with a panic() here or there to handle fatal
issues, and there's never a shortage of ignorant fools dropping in to lecture
me.

If I'm not supposed to use it, why is it even in there? To tempt me? That
would fit with the glorification of pain and boredom, I guess.

------
alexbanks
This thread is rife with "Go should have Try because I want Try" that also
seem to be made by developers that do not write Go. It seems confusing to me
that voices generally involved from Go are so demanding of its maintainers.

Curious, are there full-time (or at least Primary) Go developers that are
upset by the lack of Try?

~~~
apta
> Curious, are there full-time (or at least Primary) Go developers that are
> upset by the lack of Try?

I use golang at an employer. error handling in golang is verbose, error prone,
distracting, and difficult to make sense of when there is actually an error
(composing). I've seen on several occasions now errors being mishandled
(either dropped by accident, or by overwriting already existing error
variables in the same scope). These issues don't happen with exceptions.

~~~
abhink
It's interesting that as a full time Go developer myself, my experience has
been very different about the points you have mentioned. I personally think
verbosity is good. For one, lack of verbosity catches my attention. If I am
reviewing code that looks like some part of it is ignoring the error, I would
try to find a reason about that error check exclusion. This also prevents
errors being dropped by accident.

About overwriting, again this has never been a problem for me (as far as I can
recall). Error values are very much localised in almost all Go code I have
seen and written. If a function returns an error it is either immediately
acted upon or returned by the caller. As such, any overwriting done at a later
stage is more or less for convenience sake.

~~~
fredmorcos
> This also prevents errors being dropped by accident.

Instead of designing a language that prevents errors from being dropped by
accident, hire a bunch of literal gophers to search your codebase for
inverbosity. Good plan will work 10/10 times.

------
cwojno
Personally, I feel that the motivation for the issue is one of convenience.
The most common use case is changing the flow control in the event of an error
to return from the current stack. I'm not a fan of defining an error handler.
This seems far too intrusive and cumbersome and a bridge too far.

GoLand gets around this somewhat by adding in the Live Template of "err" being
a macro expansion for the if err != nil { return }. However, it still adds
those 3 lines below each requisite call.

If they added a new keyword that took the place of this macro, would that be
too intrusive? Clearly, this only works if you have named return values.

It would be nice if there was a bash/Ruby-like chaining, such as:

    
    
      var1, var2, var3, err := call1(args) &&
      var4, var5, var6, err := call2(args) &&
      ... and more calls and so on ...
      return callN(args)
    

Where "&&" would perform the if err != nil { return } in-line. Should the
first call return a non-nil error as the last argument, flow would be returned
to the caller. If not, the flow continues as normal.

This could even be extended in the case of function chaining:

    
    
      return call3( call2( call1(args) && ) && )
    

The downside of using && is that it's overloading the && and may cause some
compiler/developer confusion. This was just an example based on a familiar use
case (bash); another token can be used.

Rasky suggested something like this based on another proposal in the thread,
but using "?" instead.

Another way to approach this might be an overlay language analogous to Kotlin.
There could be a Go dialect that compiled into Go that provided this feature.
Or, possibly an optional "plugin" for go's compiler that added an intermediate
transformation step prior to compilation based on new keywords, but this will
frustrate debugging and lead to other surprises. Generators tried to do this,
but it doesn't seem to have taken off, from the repos I've read.

Just my 2c

------
mattxxx
I think this is the right decision. Error-handling the go-way focuses on
_whether_ a single operation failed, rather than _how_ a set of operations
failed.

In Python or Java, you see a lot of try-catches around blocks of code, which
can obfuscate where the _source_ of the error is, despite having particular
handling for the _type_ of error.

For systems code, I mostly care about whether something failed... like: \- Was
the socket opened? \- Was the file created? \- etc.

------
quotemstr
With try, Go might have been a language I'd have enjoyed using. It's a shame.
Right now, I see Go as being anti-abstraction and anti-cleverness, and I'd
rather not work on codebases in which the language of choice is designed to
deter creativity and encourage monotony. Heavy use of Go is a big negative
when I evaluate potential projects to work on.

~~~
CraftThatBlock
Anti-cleverness is what makes Go great. Go code is simple, and it is easy to
read/understand. It's a feature of the language

~~~
z0r
Go code is all about being able to see the trees, forget about the forest. Go
programs are rarely easy to read or understand unless they are written by
exceptionally talented developers in my experience.

~~~
apta
Agreed. Because golang is "simple", the actual code base becomes a mess,
littered with 10 line functions that are basically map/filter/etc. calls,
which can be written on a single line in a proper modern language. The
majority of golang code bases I've seen are much more difficult to follow
compared to if they had been written in something like Java or C#.

------
drivebyops
Great that they listened to the community

~~~
kjksf
They listened to the part of the community that agrees with you.

I'm "community" and I would prefer to have `try()` than not to have. They
didn't listen to me.

~~~
libria
"Listened to" is colloquial for "engaged with". They engaged with people who
represented the `try()` option and declined to implement it.

It is impossible to implement every proposal their ears listened to so
obviously that's not what listened means.

~~~
FireBeyond
And they also "fast tracked" the decline "ahead of schedule".

------
willbw
Try not. Do. Or do not. There is no try.

------
segmondy
Lack of try and go's error handling is part of my attraction to go. Explicit
handling of error is much better than implicit and having to guess how things
might get handled.

~~~
ngrilly
try may have drawbacks but not this one. try is perfectly explicit. There is
no implicit error propagation.

------
badrabbit
Has the idea of making the returned error implicit and handling(or discarding)
of the return value mandatory been explored? Thinking on the lines of errno in
C.

------
kerng
Well, that seems like the most obvious feature add to Go.

------
mvndaai
I think the proposal's intent was great but had some serious issues.

I created my own proposal that I think addresses the issues. I would love some
constructive feedback.
[https://github.com/golang/go/issues/33161](https://github.com/golang/go/issues/33161)

------
staunch
Thank you Go developers! Please keep the language minimal, opinionated, and
never subject to the bikeshedding mobs!

It wouldn't have been the end of the world but I'm glad to see Go still has
alive the spirit that made it so popular in the first place.

------
dr01d
This is why we have nice things!

------
politician
I'm extremely happy about this, and am hopeful this approach to community
feedback continues. I previously said that this was a fait accompli like Go
modules, but now I will happily withdraw that criticism.

/cheers

------
atombender
I appreciate this change of heart, as I wouldn't want a half-baked solution to
make it into Go, but I hope they don't just stop there.

What the try() proposal gets wrong is that it tries to automatically bounce
the error back the stack, being equivalent to "if err != nil { return }",
which is ofte reasonable, but at the same time lacks the flexibility that
current Go code has in terms of augmenting the error, or indeed handling it:
You'd end up with try() calls for _some_ things, but not all, so it's just
ironing out one wrinkle and not every wrinkle. The second argument to try() is
too heavy-handed. What we need is easy, readable syntax for all cases.

In current Go code, people like the "v, err := someFunc()" syntax because it
allows the happy path to stay in the current scope, with the secondary syntax
"if v, err :=" introducing a new scope to avoid polluting the parent scope. If
you're in the current scope, your code stays flat and clean (although Go's
love of shadowing cause subtle).

In my opinion, we need a syntax that is similar to a "catch" block in other
languages, but easier. Perhaps something like this:

    
    
      v := getName() check err: {
        return errors.Wrap(err, "could not get name")
      }
    

This allows you to handle the error and augment it, while not introducing
anything magical. It's exactly equivalent to an "if v, err :=", but without
introducing a new scope _and_ not polluting the current scope with an error
variable. This syntax would happily support fallthrough:

    
    
      v := getName() check err: {
        log.Printf("could not get name, ignoring: %s", err)
      }
      // v is now the zero value
    

And of course you could still support a syntax for introducing a new scope:

    
    
      if v := getName() {
        // v is now valid
      } check err: {
        // You can return or anything else
      }
    

You could easily extend it to error types with some kind of matching syntax:

    
    
      v := getName() check err == io.EOF {
        // On EOF
      } check err: {
        // All other errors
      }
    

The above syntaxes don't support chaining. I'm on the fence. I like Rust's "?"
postfix syntax, and I think it could work:

    
    
      // This could be "monadic"; the first error short-circuits
      ceo := getPerson()?.getCompany()?.getCEO() check err: {
        return errors.Wrap(err, "couldn't get CEO")
      }
    

But why not just let "." be smart about errors and fall through like this?

    
    
      ceo := getPerson().getCompany().getCEO() check err: {
        return errors.Wrap(err, "couldn't get CEO")
      }
    

After all, "." can know if the function returns multiple values, and "." on a
tuple (or whatever Go calls it) isn't valid, so why not "promote" the "." to a
high-level operator here?

The awkwardness of the try proposal goes deeper than just error handling, too,
I think. One reason Go can't solve its error handling problem _elegantly_ is
because of its reliance of multiple, exclusive return types. Almost all
functions with this signature:

    
    
      func getName() (string, error)
    

...obey the unwritten rule that the function returns _either_ a valid value,
or an error. If you get an error, the value is supposed to be unusable, and
the way to check for it is to look at whether there was an error.†

In many type systems, a situation where a return value can be _either_ A or B
is variously called a union, or discriminated union, or enum, or sum type,
which is the term I prefer. It's always bugged me that Go's designers didn't
go one step further and made sum types a first-class citizen, because I think
it would fit Go rather nicely. TypeScript solves it this way:

    
    
      function getName(): string | number
    

And you can also define types this way:

    
    
      type number = int | float64
    

This, incidentally, introduces the idea of optional values:

    
    
      function takesOptional(x: string | null)
    

Back to error handling, it would be more natural for a function to declare
itself this way:

    
    
      func getName() string | error
    

After all, it returns a value _or_ an error. It can never be the superposition
of both.

In this regime, anything that gets a value needs to explicitly check for what
it is. "switch" on type would work, just like today, but that gets verbose for
mere errors. So we just say that the "check" syntax as outlined above would
work for any union that involves an error. In other words:

    
    
      func getThing() Person | Company | error {
        ...
      }
      thing := getThing() check err: {
        return errors.Wrap(err, "can't get thing")
      }
    

We can do this because we can say that "error" is special and known to the
compiler, just like make() etc. are special.

Of course, sum types go beyond errors, and open other avenues that are very
poorly served by Go right now.

\---

† This, unfortunately, isn't universally true. For example, io.Reader's
Write() method's contract says that if it returns io.EOF, it may still have
read a final amount of data into its buffer. Many get this wrong and ignore
the data read. Which points to the problem of conventions that only _seem_
like unwritten rules, but also highlights how the lack of strong type-system
support creates the issue in the first place.

------
hnaccy
>The key point here is our programmers are Googlers, they’re not researchers.
They’re typically, fairly young, fresh out of school, probably learned Java,
maybe learned C or C++, probably learned Python. They’re not capable of
understanding a brilliant language but we want to use them to build good
software. So, the language that we give them has to be easy for them to
understand and easy to adopt.

~~~
parhamn
The average skill of most software teams is probably not far if not lower.
I've been on very good ones and very bad ones -- lots of the code becomes
least-common-denominator/weakest-link quality.

~~~
skybrian
This isn't about good and bad, it's about experienced versus beginner, for
certain values of "beginner". Not complete novices, but people who have
already been programming in some other languages, but are new to Go. Think new
college grads.

------
RandomInteger4
"Do or do not. There is no try." ~ Golang master Yoda

------
kadendogthing
This reads like script kiddies who became professional programmers and just
want things done how they've always been done.

Error handling in go is not concise, and calling it explicit is in insult to
anyone with a modicum of abstract reasoning skills. Error handling in go is
not explicit, it's verbose. Error handling in go is not concise, it's broad.
Error handling in Go is not consistent, it's an exercise left up to diligent
programmers.

As golang's code base grows, you're going to see this mistake play out over
and over and over again.

Having a Result<T> construct would have been great for go.

------
patientplatypus
I mean...

If you don't like writing `if err!=nil {...}` throughout your code base surely
you can just create some middleware function in its own package that has
switch statements based on error cases. Checking for errors frequently in the
running of the code is more or less a Good Thing.

~~~
mevile
I don't see how a switch statement helps you avoid checking for errors when
methods you use return errors, and a lot of methods return errors.

~~~
patientplatypus
You can pass functions to functions
([https://play.golang.org/p/XNMtrDUDS0](https://play.golang.org/p/XNMtrDUDS0)).

So you can do (sans syntax):

    
    
        ErrorCheckerFunc(fn myParams -> MyFunc(myParams), "message")
    
        ErrorCheckerFunc(fn myParams2 -> MyFunc2(myParams2), "message2")
    

etc...

and then the actual function:

    
    
        ErrorCheckerFunc(fn myFunc, message){
         returnVal, returnError = myFunc();
         switchError:
          case error is foo:
           print error
          case error is bar: 
           log error print "error logged"
          ...
          case n+1 etc
    
          // you can handle message in a similar way to get 
          // cases by function names, and if you have elixir
          // style quote unquote metaprogramming in Golang you might just be able to get function names based on passed function - I don't think this is in the language spec though it may be possible to do some other way.
    
          if error not nil do switchError(error)
           return RuhRoh
          else
           return returnVal
        }
    

It's not so bad, it's just a middleware.

~~~
djur
I'm not clear on what exactly you're trying to represent here, but it looks
like something that wouldn't work with Go's type system. ErrorCheckerFunc
would need to always have the same return type and accept a function with the
same type signature.

~~~
akavi
If only there was a way to make a single function work with multiple different
types. A way to "genericize" it, you might say.

Alas, that's obviously completely impossible. Such a thing is beyond the
capabilities of us mortal programmers. But maybe one day PL researchers will
discover a way. One day...

~~~
thatswrong0
Rob Pike would rather use a for loop than map or filter.. so yeah don’t dream
too big

[https://github.com/robpike/filter](https://github.com/robpike/filter)

~~~
mevile
That's kind of awesome code though. I learned things about go looking through
how he implemented reduce. Thank you for sharing it.

------
hnruss
Do or do not, there is no 'try'.

------
poweroftrue
Thank God!

~~~
mutt2016
God codes in C, and uses three space tabs.

~~~
alexanderdmitri
I'd definitely be interested in knowing what His take on error handling is.

~~~
grogenaut
It's undefined and implementation specific.

Or maybe she took the approach Linus did with git and just let it crap out and
start clean next big bang.

------
eweise
No wonder people are leaving in droves for Go++.

------
Zenst
I hope somebody submits a "Do" and a "Do Not" amendment. Think of Yoda.

