
Next steps toward Go 2 - rom16384
https://blog.golang.org/go2-next-steps
======
tschellenbach
Go so far has always prioritized the things I care most about:

\- performance & high concurrency \- a small language feature set so I can
hire & train awesome programmers with a different background \- practical
solutions, with a focus on getting things done

I feel confident that with Go 2 they will continue to go down this road. The
`try` syntax looks kinda confusing with the implicit return. On the other
hand, nothing too crazy.

~~~
unscaled
The "Go is performant" gospel seems to be coming from people who came from
Python and JavaScript to Go. I don't think Go prioritizes performance more
than most other popular statically-typed language Java[1], Haskell or Swift
(or less popular ones like Ocaml).

And Go obviously doesn't prioritize performance as much as C++ or Rust. Case
in point is with very same error handling proposal discussed here. You're
going to have to pay a performance price for using a handler since the Go
compiler not only avoids inlining defer blocks (if I understand correctly),
but even allocates dynamic memory for each block. Meanwhile, equivalent RAII
in C++ has no overhead over manual code.

Yes, the Go compiler team is now working on working on fixing this issue, but
defer is not a new language construct and they should have started fixing that
long ago, considered how often it's being used. What's worse, they still
didn't make it a stated goal to eliminate the overhead of using defer
completely, but only talk about reducing overhead in common cases.

This approach makes me sad, because Go does have the engineering power and
stated design goals to be more serious about performance. Yeah, I feel
confident Go will keep improving, but just the same as Java does (without the
same hype).

[1] Although until recently they did have radically different choices about
which kind of performance to optimize, e.g. Java went up with throughput
(Throughput-optimized, JIT tricks for fastest execution after warmup), while
Go went for low worst-case latency and faster startup time.

~~~
throwaway34241
I'm not GP, but I like Go because it prioritizes performance _in addition to_
compile times, ease of use, safety, etc.

I don't think anyone is claiming that Go will outperform well-optimized C++,
if you're willing to write your server software in it.

Of the languages you mentioned Java would be the most typically considered
alternative for server software. You mentioned Go's low latency but there's
also some other strengths like more control of memory layout, slices make it
easy to avoid copying in APIs, Goroutines can be lighter weight than Java
threads, etc. But as long as Go is roughly comparable I think it's fair to
consider Go performant, since Java itself is one of the most mature and
optimized languages for server software.

~~~
jbergens
An interesting question is how the compile times in Go compares to those of
Java and C#. If the language is somewhat easier, but not very easy, and
compiles faster but not really that much faster, it might not be a good way
forward for Java and C# developers.

I've always assumed that the compile time comparisons were made against C++
since Google had a lot of large systems written in C++ that they said took a
long, long time to compile.

Today I think C# using .NET Core is a good general language. It is easy to
learn, fast and compilles pretty fast. It also handles concurrency with
async/await or threads. It still has longer start up time than Go, like Java,
and might use a bit more memory but should work for most systems.

~~~
GoblinSlayer
JIT time disappears after a warm up, but cold start of a .net application is
massive.

~~~
pjmlp
AOT has always been an option on .NET, even if until Windows 8 it lacked some
love.

Additionally it has been available in Mono and other runtimes as well.

------
_hardwaregeek
So wait, they're introducing magic "function" that has a variable number of
generic return arguments? Huh. In Rust the try! macro makes sense because A.
there's a precedence of macros and B. there's a precedence of generics. But in
a language with neither macros nor generics, this just seems odd. Kinda like
making the Justice League movie without any of the requisite standalone
movies.

~~~
jjoonathan
Isn't "generics for me but not for thee" already a core part of the Go
philosophy?

~~~
aikah
No, you are trying to justify odd builtin functions choice in retrospect.
Nobody has ever said that Go "secret generics" are a core part of the Go
philosophy but you.

The reason they exist is because Go language designers could not do without
them. The same people that have claimed for years that Go didn't need
generics.

~~~
icholy
> The same people that have claimed for years that Go didn't need generics.

Considering all the successful software that's been written in Go, they
weren't exactly wrong.

~~~
aikah
> Considering all the successful software that's been written in Go, they
> weren't exactly wrong.

Considering all the successful software written in PHP...? Or Javascript...?
Java...? Your argument absolutely has nothing to do with my point.

~~~
icholy
I think you're confusing "want" with "need"

------
weego
I dislike the try implementation, one of Go's strengths for me after working
with Scala is the way it promotes error handling to a first class citizen in
writing code, this feels like its heading towards pushing it back to an
afterthought as tends to be the case with monadic operations

~~~
kbd
It's _just_ syntactic-sugar. From the proposal
([https://github.com/golang/go/issues/32437](https://github.com/golang/go/issues/32437)):

    
    
        f, err := os.Open(filename)
        if err != nil {
            return …, err
        }
    

is simplified to:

    
    
        f := try(os.Open(filename))
    

> I dislike the try implementation

What would you suggest instead?

~~~
politician
It doesn't matter what we suggest. There are 100s of suggestions on that Git
ticket, and it's a fait accompli masquerading as an RFC.

Down-vote if you want, but Go modules package management was handled in the
same way. It's not without precedent.

~~~
CamouflagedKiwi
And hopefully try will have the same outcome, i.e. something significantly
better than existed in the ecosystem before.

Basically I agree that the process for modules may not have been ideal, but
the outcome was pretty good. We could do a lot worse than getting the same
again.

~~~
losingthefight
I mean, everyone is entitled to their own opinion, but I actually liked dep
and never had a problem with it. With Go modules, it has been a massive
problem converting our microservices over to Go modules, especially because
some dependencies like Resty caused a lot of headaches and I lost several man
weeks going through the process. Dep worked and modules doesn't seem to have
brought us any real benefit, but again, my use case is my own and perhaps for
you module was a better experience.

~~~
atombender
Go modules have some (minor, in my opinion) issues, but Dep was a total
nightmare.

Dep was made by one of the authors of Glide, which coincidentally suffered
from many of the exact same bugs, maybe because it reused some of the same
code. Our entire team constantly had "dep ensure" failing in unpredictable
ways (locally or on the CI server), usually caused by the solver not
understanding something. These failures were completely incomprehensible and
not fixable by the user. Dep's solver was also very slow.

From what I could tell, it was a combination of shoddy engineering and trying
to do too much; if I remember correctly, Dep and Glide both tried to
automatically detect and convert dependencies that used competing tools like
Godep, which didn't work very well. For a long time, Dep didn't work _at all_
with the Kubernetes client library, for example.

We've had almost zero problems with Go modules. I've encountered some minor
bugs, but unlike Dep, nothing worth tearing my hair out.

The drama around Dep vs. Russ Cox shouldn't have happened, of course.

~~~
no_wizard
Any good documentation on using go modules day to day?

I have found the package management story around go so confusing its really
stopped me from trying to use the language.

For context, I'm used to how composer/npm install the modules into a folder
locally. I just can't seem to figure the go way of doing modules where I don't
get completely confused.

~~~
atombender
The official wiki is good:
[https://github.com/golang/go/wiki/Modules](https://github.com/golang/go/wiki/Modules)

This article looks like a good intro: [https://roberto.selbach.ca/intro-to-go-
modules/](https://roberto.selbach.ca/intro-to-go-modules/)

The equivalent of "npm install" is "go get" (optionally "-u"). You can also
edit go.mod manually. Like NPM, this must be done within an application's
root.

A point of confusion might be the difference between a module and a package. A
package is just a folder that declares "package foo" at the top in all its Go
files. A module is the closest analogue to an NPM package. Similar to NPM, a
single Git repo can contain many nested modules. Unlike NPM, modules can be
imported with Git -- no need to publish to a special registry.

When a Go file imports a package, the referenced package might live in a
module outside your own. It knows it's outside your module because go.mod
defines the full root path (e.g. github.com/foo/bar/baz) of your module.

You might experience some confusion when you look at how the new module system
interacts with the old GOPATH way. Go current supports both modes.

------
AtroxDev
Looks like they really plan to move forward with the `try` function... I'm
personally not a fan of it, I hope they listen to the feedback they received
on the GitHub Issue and not just push it through like the error inspection
[0].

[0]:
[https://github.com/golang/go/issues/29934#issuecomment-48968...](https://github.com/golang/go/issues/29934#issuecomment-489682919)

~~~
kenhwang
It looks like instead of making a better error handling system, they just made
it easier to not type `if err != nil` everywhere. Then there's all that
handler stuff that looks very much like Java's `try ... catch` in reverse
order.

Pretty underwhelming for what's supposed to be a modern language.

~~~
zellyn
Is Go “supposed to be a modern language”? I'm not sure even the language
designers would agree with that characterization.

~~~
Thaxll
What is a modern language anyway?

~~~
pjmlp
One that takes into account features that have made into the mainstream during
the last 20 years, instead of feeling like an Algol-68 subset.

~~~
Thaxll
I don't care about the features in the last 20 years if I'm able to do my job
efficiently which Go as a language provides. Never wonder why those great
academics languages with a ton of features are not adopted?

~~~
pjmlp
So now languages like Java, Swift, Kotlin are academic languages?

~~~
fwip
Java is like 25 years old, you know.

~~~
pjmlp
I know, but apparently it has too many academic features not worthy of Go's
adoption.

------
imetatroll
I use golang quite a lot. The way this _should_ be used is being ignored by
all of the examples I have seen so far.

When an error occurs people should be doing what they already should be doing.
That is to say, immediately log/wrap the error with context (file + line
information) so that the error can be immediately found. Then if there are any
callers of this failing function, they are the ones who should use try.

    
    
      func A() error {
        if err := do(); err != nil {
          log(err)
          return err
        }
        return nil
      }
    
      func B() error {
        try(A())
        return nil
      }
    

To be honest though, I don't personally like this proposal.

------
ameixaseca
The proposal for `try` made my eyes bleed. It combines a number of features
from metaprogramming and generic types but in a hard-coded manner and doing a
number of assumptions about functions signatures, etc.

I guess that's the idea of Go, to restrict the language power by giving just
the functionality the designers think the programmer should have, but to not
allow anything too fancy.

However, if you put some thought to it, it really sounds silly when you know
that this will only limit your ability to actually use the language and not
really "simplify" anything, since it only adds to the general cognitive load
of the language - in this case, one more exceptional case.

~~~
swiley
Having read a decent amount of go code I'm really quite convinced the
restrictions are beneficial. Personally I find it easier to read than just
about any other language.

~~~
qtplatypus
Can I politely suggest that the fact that you have read a decent amount of go
code is directly related to the fact that you find it easy to read? I’ve read
and programmed in many languages and apart from some outlayers the more I’ve
read that language the more readable it is.

------
baby
My biggest concern is how they will implement generics and if it will lead to
generics being abused and making codebases worse. FWIW, eventhough Go is less
expressive than certain languages, it wins in clarity and readability.

~~~
aikah
> Go is less expressive than certain languages, it wins in clarity and
> readability.

Not having to deal with inheritance is clear and readable enough. Generics
wouldn't make the language less clear and readable. Furthermore, it's not an
ALL or NOTHING case. Generics could be restricted to generic functions for
instance, not types per say. Go already makes a lot of trade-offs, i don't see
why this trade-off would be controversial.

But to me generics are the least of Go's problems. A lot could be done to make
the language easier to use, like co-variant interfaces for instance, a tons of
other stuff could be added in a relative "invisible" fashion that would not
impact the language's syntax.

------
infogulch
Some don't like the magic, and some are worried about losing annotated errors.
If you can tolerate just a _little_ bit more magic, you can solve the error
annotation problem:

    
    
        f := try(os.Open(filename), "opening config file")
    

Place the context string just after the last, error parameter. That is, make
try also also allow the pattern: try( a1, a2, ..., error, string ). Bonus
points if the last arg could be a _func(error) error_ , that way you can pass
a local closure to wrap the error in a scoped context and also avoid pre-
calculating an expensive error string even in the non-error case.

It's just a language feature, you can make up any interface you want. We're
already on the magic train, might as well ride it.

~~~
agnivade
All of these have already been discussed in the GH issue. It is all being
taken into consideration. Check out
[https://swtch.com/try.html](https://swtch.com/try.html) for a thematic
grouping of the issue.

~~~
infogulch
That's a heck of a reference, thanks for sharing! And there it is
[https://swtch.com/try.html#args](https://swtch.com/try.html#args) under
Alternative: Add arguments to try. Seems I have some reading to do.

------
cpeterso
An interesting Go2 proposal that didn't make the cut is "change int to be
arbitrary precision", i.e. make the builtin int type a bigint to avoid integer
overflow and conversion bugs:

[https://github.com/golang/go/issues/19623](https://github.com/golang/go/issues/19623)

------
butterisgood
Hmmm so Go is getting monadic behaviors with "try" to hide error handling
issues with examining every single one with "if".

I think this is a rather large improvement to error handling. I need to read
more about how it works with "go" and "defer" (it's all in there).

~~~
sagichmal
An improvement, really? It seems to me that it reinforces a bad pattern
(returning an error directly without annotation) and will make return analysis
of functions in code review a lot harder. For what, to reduce typing? Doesn't
strike me as a good tradeoff, and certainly feels at odds with Go's principles
so far.

~~~
butterisgood
I'm not sure I'm getting what you're saying.

Try can only be used in functions that use error types as the last returned
type. The compiler should be rejecting uses outside of that. So why is this
any harder than thinking about "go" or "defer"?

~~~
sagichmal
Currently the only way out of a function is via the `return` keyword, or,
exceptionally, panic. Those are easy to spot and easy to understand. With try,
especially if it's nested in a complex statement e.g.

    
    
        fmt.Fprintf(os.Stdout, "%s: %d\n", try(getID()), try(getCount()))
    

a function may return in surprising places.

~~~
qtplatypus
I'm not sure that they are easy to spot.

    
    
       fmt.Fprintf(os.Stdout, "%s: %d\n", getID(), getCount())
    

Could have hidden panics in it and you would never know.

~~~
sagichmal
Yes, but panics are exceptional, and not something you need to worry about
when doing a normal code review. Try would be commonplace.

------
courtf
There's a lot of talk in these comments about the new 'try' built-in and error
handling in general which gives me the sense that errors are more
controversial and in need of criticism than the proposed generics design.

I see it the other way around. I'm in favor of generics for Go, but I can't
support this design and I think it will have a negative effect on library
stability.

Rather than narrowing the scope of contracts to remain mostly orthogonal to
existing features, they have it totally open-ended. They clearly didn't want
to put too much restraint on what can be stipulated by a contract, and
consequently it just feels like a solution looking for a problem (or like they
didn't want to to make any hard decisions for which they might be critcized).
With contracts as they are, they are incredibly powerful and stomp all over
the type system and interfaces in terms of their practical utility. This is
not orthogonal design, or really any sort of design, it's a blank page. They
feel like a complete abdication of the designers' responsibility to make Go a
simple language that tries to only pull in minimal features while reducing
overlap. It's context.Values again but far worse. Please stop trying to make
everyone happy, it's impossible (and I realize the irony here).

I liked generics when they were going to be super strict (and consequently
super simple from the user's perspective), no upper or lower bounds on the
type, just essentially a glorified gogenerate. This feels like you all are
trying to answer to the critics rather than to the code.

------
remon
Not a huge fan of the try built-in spec as proposed but I struggle to
understand the reasoning behind people arguing that the current error
checking/handling paradigm "at least makes the code readable/understandable".
It doesn't. The whole issue with golang error checking boilerplate is that
your brain will start seeing it as noise, which may obfuscate issues within
the code. This is especially true for code you have not worked on yourself.

~~~
JulianMorrison
Yeah, and this new change doesn't alter how explicit the code is. The new
"try" just means "return error from here without setting the LHS if the RHS
returns an error". It is the standard boilerplate captured.

When this is in common use, it will greatly improve the visibility of _non_
boilerplate error handling.

------
azhenley
Go 1.14 will include the following "smaller" proposals:

\- A built-in Go error check function, “try”.

\- Allow embedding overlapping interfaces.

\- Diagnose string(int) conversion in go vet.

\- Adopt crypto principles.

~~~
hosay123
> \- Adopt crypto principles.

Turn a profit by pumping crappy tech to the masses?

~~~
azhenley
If you are actually interested in the proposal:
[https://github.com/golang/go/issues/32466](https://github.com/golang/go/issues/32466)

------
conroy
Here's a short example comparing existing error handling, the check proposal,
and the experimental try proposal

[https://gist.github.com/kyleconroy/e48c83425349f2d3954d4e764...](https://gist.github.com/kyleconroy/e48c83425349f2d3954d4e76471ff3f5)

~~~
kodablah
Why is there a defer in the "try" example? To just show wrapping even though
the "existing" one doesn't wrap? Also, the "try" example doesn't defer-close
"w" in case copy fails. Seems that they aren't the same example.

~~~
ixwt
try() implicitly returns when there's an error, and sets the error in the case
of an error. Then the defer statement would handle the error, as the return is
happening.

~~~
kodablah
I'm saying it doesn't match what "existing" is doing which doesn't wrap the
error. If you want comparable examples, they need to be doing the same thing,
not one wrapping w/ an Errorf and another not.

------
rdm_blackhole
I am not a big fan of "magic" happening behind the scenes.

I understand that the current error handling is a bit too verbose but at least
it makes the code readable.

I am not sure if this is really the best way to fix this issue TBH.

------
yingw787
I'm not familiar with golang, but I did read about a concern with variable
mutability and race conditions when communicating across channels:
[https://bluxte.net/musings/2018/04/10/go-good-bad-
ugly/#muta...](https://bluxte.net/musings/2018/04/10/go-good-bad-
ugly/#mutability-is-hardcoded-in-the-language)

""" As we saw above there is no way in Go to have immutable data structures.
This means that once we send a pointer on a channel, it's game over: we share
mutable data between concurrent processes. Of course a channel of structures
(and not pointers) copies the values sent on the channel, but as we saw above,
this doesn't deep-copy references, including slices and maps, which are
intrinsically mutable. Same goes with struct fields of an interface type: they
are pointers, and any mutation method defined by the interface is an open door
to race conditions.

So although channels apparently make concurrent programming easy, they don't
prevent race conditions on shared data. And the intrinsic mutability of slices
and maps makes them even more likely to happen. """

Is this still a concern, and if it is, would it be addressed in Go 2?

~~~
blaisio
That isn't really the sort of thing Golang is trying to address, at least, not
directly. Go is very unlikely to add immutable data structures at this point.
And Go is not built to prevent race conditions. There is a race detector that
can detect many race conditions in running programs, but nothing in the type
system.

~~~
yingw787
IMHO type systems are pretty much impossible to change after the fact (see:
Python's `str` type and the whole 2/3 migration fiasco), which is why I
thought to bring it up since it's a major version change. I think a lack of
support for efficient immutable types gets in the way of `golang` being a
concurrency-first language, if the language endorses CSP with native support
for goroutines and channels in the first place.

How you can write simple, reliable, and efficient user code
([https://golang.org](https://golang.org)) if using your favored concurrency
model results in race conditions?

~~~
Thaxll
99% of languages don't have race condition prevention and yet they're doing
fine. Go has a pretty good race condition detector.

~~~
zeeboo
Indeed, I believe it's impossible to statically prevent race conditions in a
turing complete language. Data races can be prevented, but that's just a
subset of all possible race conditions.

~~~
Quekid5
I think Erlang does? I don't really know Erlang, but AFAIK there's no way to
share anything mutable between processes. Any data which is shared is
(semantically) copied when it's sent to a different process. (Ignoring FFI,
and such.)

Same thing applies to the STM "subset" of Haskell -- conformance to which is
statically verified by the type checker[1].

[1] There's no particular magic going on wrt. checking -- STM is a monad. It
just happens to have "magic" runtime support.

~~~
zeeboo
Those disallow data races, not race conditions. I find that this blog post
explains the distinction well:
[https://blog.regehr.org/archives/490](https://blog.regehr.org/archives/490)

~~~
Quekid5
Ah, yes. My bad... I missed that crucial detail. Still, maybe a non-TC
protocol language could fulfill the criteria?

------
arendtio
Does someone know if there is a good reason why try uses parenthesis? I mean,
wouldn't it look even cleaner without them?

    
    
      f := try os.Open(filename)

~~~
tptacek
One possible reason is that funcall semantics will probably change (Go 2 is
likely to have generics), and so simple funcalls keep the language orthogonal;
new operators are forever.

~~~
krferriter
> Go 2 is likely to have generics

Big if true. It's one main complication keeping me from adopting Go for more
things.

~~~
tptacek
It's mentioned in the post we're commenting on.

------
smitty1e
I sure am glad that no one did a "Go 2 considered harmful" gag.

~~~
alecmg
came here just to make that joke :(

------
tjholowaychuk
I'd rather have nice command output, that could use a lot more love than this
try() stuff, and maybe ditching the weird go.mod format it doesn't really add
value—if anything it hurts because you can't manipulate those files with
regular codecs.

When it comes to error context it would be nice to facilitate structured
logging, rather than arbitrarily formatted strings.

------
icholy
Argh ... check/handle is so much better

------
zupa-hu
Ah, contracts seem ugly. If only operators could be method names we would not
need contracts, we could just define generic types like an interfaces:

    
    
        type T generic {
          +(T) (T)
          ==(T) (bool)
        }
    

Donno where to suggest this.

------
tracker1
(User Defined) Generics support.

edit: clarify.

------
zerr
Why I would switch from Perl 6?

------
invasionofsmall
I have been using for a year. It's a glorified bash script. Let's hope it will
become something more similar to a programming language.

