
Go 2 Draft Designs - conroy
https://go.googlesource.com/proposal/+/master/design/go2draft.md
======
pcwalton
Looks good. The control flow of "handle" looks a bit confusing, but overall I
definitely like the direction this is going.

The decision to go with concepts is interesting. It's more moving parts than I
would have expected from a language like Go, which places a high value on
minimalism. I would have expected concepts/typeclasses would be entirely left
out (at least at first), with common functions like hashing, equality, etc.
just defined as universals (i.e. something like "func Equal<T>(a T, b T)
bool"), like OCaml does. This design would give programmers the ability to
write common generic collections (trees, hash tables) and array functions like
map and reduce, with minimal added complexity. I'm not saying that the
decision to introduce typeclasses is _bad_ —concepts/typeclasses may be
inevitable anyway—it's just a bit surprising.

~~~
the_duke
I don't like the possibility of nested handle() blocks.

This could make grasping the control flow in a function very cumbersome.

Everything else looks like a very good change to me and would draw me back in
to Go.

~~~
jerf
I've thought about that before, as I participated in one of the bugs where we
chewed on these various matters, and I came around to the conclusion that in
general, understanding what is in your current scope is just the cost of doing
business. Being scope-based is the minimal cognitive load you can hope for.

I also strongly suspect that in the general case, you're not going to see more
than one handler at the top of the function. It will be a rare function that
has a dozen of them in there, and either A: it needs to be refactored, and
while I'm sorta sympathetic to the idea that we shouldn't hand programmers too
much rope to hang themselves with, that argument has the problem that you end
up removing pretty much everything from the language or B: it's the core inner
complicated loop of a program and it just needs that much complexity, which
still doesn't mean it's going to show up in every function.

It's not that dissimilar from one function getting nested four layers deep in
try/catch statements; yes, it would be a bit confusing to disentangle what
that would actually do (moreso, since the catch statements are conditional but
the handlers are not), but the answer is simply not to do that unless you
really need to, and thwack any coworker that tries it on the wrist during code
review. It is impossible to make a Turing-complete language in which I can't
express something incomprehensible in.

~~~
the_duke
I actually like the explicit error handling idiom a lot.

(especially in Rust now that is has been made convenient with the ? operator
and the failure crate which provides backtraces and wrapping).

I would prefer an extension of the `check` syntax instead of the standalone
handle blocks.

For example:

    
    
        check someFn() 
        |> if err : = someFn(); err != nil { return err; }
    
        check someFn() handle {
          panic(err)
        } 
        |> if err := someFn(); err != nil { panic(err) }
    

So handle blocks are only for an individual expression.

This way the control flow is still linear and explicit (ignoring defer...) but
the syntax is much more convenient.

~~~
pcwalton
Yeah, this is the obvious approach. Just make "check EXPR handle BLOCK"
syntactic sugar for the "if err != nil" idiom. It's totally clear to the
reader what's going on.

~~~
kjeetgill
It's obvious because it's essentially just try/catch (as I'm sure you know)
but _misses the whole point_ of why they propose anything different.

The point of 'handle' is that it applies to ALL FUTURE calls to check in the
function body. If we're going to consider alternatives we need to at least
address the authors perspective (even if just to deny it).

I guess the proposal's central idea is that once you see an error, error
handling is going to be focused on rolling back various stages of partial
progress. It's the complement to how 'defer' incrementally accumulates
guaranteed execution in the face of either a return or unwind.

That said, how common this issues is and if it's worth optimizing for is more
up to debate. It would be nice to see a lexicon of well handled errors; for
90% most of what I do log and unwind is sufficient.

What do I know -- I actually like Java style checked/unchecked exceptions and
think they'd work well for Go as the parent said. For what I'm up to it's
often sufficient to just not leak partial progress via side effects. All the
partial progress just evaporates into the GC. 'finally' or 'defer' takes care
of most of the .close() or .unlock() calls.

The change I'd make to Java-style exceptions if I had a time machine is to
make all exceptions types unchecked. Instead of looking at exception's types
at all, enforce checks only if a function has 'throws' declared.

Calls to 'void close() throws IOException' would still need to be checked but
a function containing 'throws new IOException()' isn't forced to declare
throws or catch it. This lets API's be explicit and force due diligence but
lets user code intelligently allow unwinding until it's caught at the
appropriate level. Some sugar for code to acknowledge the checked error and
unwind could be nice I suppose.

I think fattening out Go's err's to full exceptions with stacktraces is more
pressing personally.

~~~
pjmlp
As someone that also likes them, I would point out that actually Java design
team took their inspiration from CLU, Modula-3 and C++ regarding checked
exceptions.

~~~
kjeetgill
Im only familiar with Java and python, do CLU or Modula-3 do much differently?
I heard C++ engineers generally hate exceptions, partially because they're
unchecked.

~~~
pjmlp
The point being that CLU, Modula-3 and C++ had checked exceptions before Java
was even a thing.

CLU never went beyond university projects, and Modula-3 research died after
the labs went through several acquisitions, DEC => Compaq => HP.

As for C++, checked exceptions never gained much support, because their
semantics are different from what Java adopted. Throwing an exception that
isn't part of the list terminates the application and there were some other
issues with them, so now as of C++17 they have been dropped from the standard
after being deprecated in the previous ones.

In general there are two big communities in C++, the ones that turn everything
on (RTTI, exceptions, STL, Boost...) and those that rather use C but get to
use a C++ compiler instead.

So no we don't hate exceptions, it just depends on which side of the fence one
is.

------
JelteF
Overall really great that Go is addressing it's current biggest issues. I do
think the argument for the check keyword instead of a ? operator like in Rust
is quite weak. Mainly because a big advantage of the ? operator (apart from
the brevity) is that it can be used in method calling chains. With the check
keyword this is not possible. AFAICT the check keyword could be replaced for
the ? operator in the current proposal to get this advantage, even while
keeping the handle keyword.

Furthermore the following statement about rust error handling is simply not
true:

> But Rust has no equivalent of handle: the convenience of the ? operator
> comes with the likely omission of proper handling.

That's because the ? operator does a min more than the code snippet in the
draft design shows:

    
    
        if result.err != nil {
    	    return result.err
        }
        use(result.value)
    
    

Instead it does the following:

    
    
        if result.err != nil {
    	    return ResultErrorType.from(result.err)
        }
        use(result.value)
    

This means that a very common way to handle errors in Rust is to define your
own Error type consumes other errors and adds context to them.

~~~
munificent
_> I do think the argument for the check keyword instead of a ? operator like
in Rust is quite weak. Mainly because a big advantage of the ? operator (apart
from the brevity) is that it can be used in method calling chains._

In Dart (as in a couple of other languages) we have an `await` keyword for
asynchrony. It has the same problem you describe and it really is very
painful. Code like this is not that uncommon:

    
    
        await (await (await foo).bar).baz);
    

It sucks. I really wish the language team had gone with something postfix. For
error-handling, I wouldn't be surprised if chaining like this was even more
common.

~~~
reggieband
Maybe I'm crazy but I'd much prefer that as:

    
    
       const temp = await foo;
       const bar = await temp.bar;
       const baz = await bar.baz;
    

But then again it looks like in this example we are returning promises from
getters on objects which is something I would avoid.

~~~
munificent
Yeah, the guidance is usually to hoist the subexpressions out to variables
like you do here. But I encounter it often enough that it feels like we are
trying to paper over a poor language syntax.

 _> we are returning promises from getters on objects which is something I
would avoid._

Getters are very common in Dart and it's idiomatic to use them even for
properties that are asynchronous. (It's not like making it a method with an
extra `()` really solves anything.) It's not as common to have nested chains
of asynchronous properties, but they do happen, especially in tests.

------
infogulch
These are all great starts, I'm glad the Go team is finally able to get some
purchase around these slippery topics!

Unless implied constraints are severely limited, I don't think they're worth
it. The constraints are part of the public interface of your generic type, I'm
worried that we could end up with an accidental constraint or accidentally
missing a constraint and having the downstream consumer rely on something that
wasn't an intended behavior.

For Go 2, I would really like to see something done with `context`. Every
other 'weird' aspect of the language I've gone through the typical stages-of-
grief that many users go through ("that's weird/ugly" to "eh it's not that
bad" to "actually I like this/it's worth it for these other effects"). But not
context. From the first blog post it felt kinda gross and the feeling has only
grown, which hasn't been helped by seeing it spread like an infection through
libraries, polluting & doubling their api surface. (Please excuse the
hyperbole.) I get it as a necessary evil in Go 1, but I really hope that it's
effectively gone in Go 2.

~~~
majewsky
How would a replacement for `context ` look?

Also, I don't share your sentiment. I actually find Context to be a quite
nice, minimal interface. Having a ctx argument in a function makes it very
clear that this function is interruptible. I certainly prefer this over
introducing a bunch of new keywords to express the same.

~~~
atombender
The problem with context isn't necessarily the interface, it is that it is
"viral".

If you need context somewhere along a call chain, it infects more than just
the place you need it — you almost always have to add it upwards (so the
needed site gets the right context) _and_ downwards (if you want to support
cancellation/timeout, which is usually the point of introducing a context).

Cancellation/timeout is important, so of course there have been discussions of
adding context to io.Reader and io.Writer. But there's no elegant way to
retrofit them without creating new interfaces that support a context argument.

Cancellation/timeout is arguably so core to the language that it should be an
implicit part of the runtime, just like goroutines are. It would be trivial
for the runtime to associate a context with a goroutine, and have functions
for getting the "current" context at any given time. Erlang got this right, by
allowing processes to be outright killed, but it might be too late to redesign
Go to allow that.

I'm ignoring the key/value system that comes with the Context interface,
because I think it's less core. It certainly seems less used than the other
mechanisms. For example, Kubernetes, one of the largest Go codebases, doesn't
use it.

~~~
infogulch
> it infects more than just the place you need it — you almost always have to
> add it upwards and downwards

> Cancellation/timeout is arguably so core to the language that it should be
> an implicit part of the runtime

This is exactly what I mean, thank you for putting it so succinctly! I would
go even further to claim that Go 1 goroutines are fundamentally incomplete due
to missing this ability to control their behavior from the outside, and
context is an attempt to paper over the missing functionality.

The key-value store built into them is also an attempt to paper over a large
gap in the language: the absence of GLS (goroutine-local storage :P).

That context combines _two_ ugly coverups of major deficiencies with the fact
that using it means doubling almost all apis and infecting the whole ecosystem
is why I dislike it so much.

~~~
atombender
Agreed. Thread-local storage has rightly been criticized as a bad idea, but
Go'a contexts are arguably worse. It'd be easier if Go has something like
Scala's implicits.

------
evincarofautumn
As a type theorist and programming language developer, I’ll admit that’s a
fairly reasonable design for generics.

I’m still a bit disappointed by the restrictions: “contracts” (structural
typeclasses?) are specified in a strange “declaration follows use” style when
they could be declared much like interfaces; there’s no “higher-level
abstraction”—specifically, higher-kinded polymorphism (for more code reuse)
and higher-rank quantification (for more information hiding and safer APIs);
and methods can’t take type parameters, which I’d need to think about, but I’m
fairly certain implies that you can’t even safely _encode_ higher-rank and
existential quantification, which you can in various other OOP languages like
C#.

Some of these restrictions can be lifted in the future, but my intuition is
that some features are going to be harder to add later than considering them
up front. I am happy that they’re not including variance now, but I feel like
it’ll be much-requested and then complicate the whole system for little
benefit when it’s finally added.

~~~
ianlancetaylor
Thanks for the comments. We tried for some time to declare contracts like
interfaces, but there are a lot of operations in Go that can not be expressed
in interfaces, such as operators, use in the range statement, etc. It didn't
seem that we could omit those, so we tried to design interface-like approaches
for how to describe them. The complexity steadily increased, so we bailed out
to what we have now. We don't know whether this is is OK. We'll need more
experience working with it.

I'm not sure that Go is a language in which higher-level abstraction is
appropriate. After all, one of the goals of the language is simplicity, even
if it means in some cases having to write more code. There are people right
here in this discussion arguing that contracts adds too much complexity;
higher order polymorphism would face even more push back.

~~~
evincarofautumn
Yeah, I’m not sure higher-level abstraction is right for Go either—like I
said, I do think the tradeoffs in the generics design are ultimately
reasonable, given the design constraints.

I guess it’s those very constraints that get to me. This is going to sound
harsh, but I believe the goal of “simplicity” is already very nearly a lost
cause for Go.

The language is large and lacks any underlying core theory, with many features
baked in or special-cased in the name of simplicity that would have been
obviated by using more general constructs in the first place; the language is
not _simple_ , it’s _easy_ for a certain subset of programmers. Adding
convenience features here and there and punting on anything that _seems_
complex by making it an intrinsic has led to a situation where it’s
increasingly difficult to add new features, leading to more ad-hoc solutions
that are Go-specific. This leads to the same problem as languages like C++: by
using Go, developers must memorise details that are _not transferable_ to
other languages—it’s artificial complexity.

A major source of this complexity is _inconsistency_. There are few _rules_ ,
but many _exceptions_ —when the opposite is more desirable. There are tuple
_assignments_ but no tuples. There are generic _types_ but no generics (yet).
There are protocols for allocation, length, capacity, copying, iteration, and
operators for built-in containers, but no way to implement them for user-
defined types—the very problem you describe with contracts. Adding a package
dependency is dead-easy, but managing third-party dependency versions is
ignored. Error handling is repetitive and error-prone, which could be solved
with sum types and tools for abstracting over them, but the draft design for
error handling just adds yet another _syntactic_ special case (check/handle)
without solving the _semantic_ problem. Failing to reserve space in the
grammar and semantics for features like generics up-front leads to further
proliferation of special cases.

Virtually all of the design decisions are very conservative—there’s relatively
little about the language that wasn’t available in programming language
research 40 years ago.

I don’t _mind_ writing a bit of extra code when in exchange I get more of
_something_ like performance or safety wrt memory, types, race conditions,
side effects, and so on. But Go’s lack of expressiveness, on the whole, means
it doesn’t provide commensurate value for greater volume of code. But it
_could_! None of these issues is insurmountable, and the quality of the
implementation and toolchain is an excellent starting point for a great
language. That’s why I care about it and want to see it grow—it has the
immense opportunity to help usher in a new era of programming by bringing
new(er) ideas to the mainstream.

------
conroy
Old and busted:

    
    
        func printSum(a, b string) error {
            x, err := strconv.Atoi(a)
    	if err != nil {
    		return err
    	}
    	y, err := strconv.Atoi(b)
    	if err != nil {
    		return err
    	}
    	fmt.Println("result:", x + y)
    	return nil
        }
    

New hotness:

    
    
        func printSum(a, b string) error {
    	x := check strconv.Atoi(a)
    	y := check strconv.Atoi(b)
    	fmt.Println("result:", x + y)
    	return nil
        }

~~~
millstone
Serious question: why is the error return from Println not handled?

~~~
jagger27
I'm having a hard time making it fail. Even with Printf, in the output it
tells you what you did wrong instead of returning an error.

In fact, the only _print function I could get to fail was Fprintf by sending
an already closed file. I suppose if it was somehow not allowed to write to
stdout the other functions might fail. But I'm not sure.

    
    
        _, err := fmt.Printf("Hello %d", "world")
        fmt.Println(err)
        
        >> Hello %!d(string=world)
        >> <nil>

~~~
ianlancetaylor
Yes, the various fmt.Print functions only return an error if Write on the
underlying Writer returns an error.

------
rdsubhas
To be frank, I was for a long time on the camp that Generics is a much-needed
feature of Go. Then, this post happened:
[https://news.ycombinator.com/item?id=17294548](https://news.ycombinator.com/item?id=17294548).
The author made a proof-of-concept language "fo" on top of go with generics
support. I was immediately thrilled. Credit to the author, it was a great
effort.

But then, after seeing the examples, e.g.
[https://github.com/albrow/fo/tree/master/examples/](https://github.com/albrow/fo/tree/master/examples/),
I could see the picture of what the current codebase would become after its
introduced. Lots of abstract classes such as "Processor", "Context", "Box",
"Request" and so on, with no real meaning.

Few months back, I tried to raise a PR to docker. It was a big codebase and I
was new to Golang, but I was up and writing code in an hour. Compared to a
half-as-useful Java codebase where I have to carry a dictionary first to learn
200 new english words for complicated abstractions and spend a month to get
"assimilated", this was absolute bliss. But yes, _inside_ the codebase, having
to write a copy-pasted function to filter an array was definitely annoying.
One cannot have both I guess?

I believe Golang has two different kinds of productivity. Productivity within
the project goes up quite a lot with generics. And then, everyone tends to
immediately treat every problem as "let's create a mini language to solve this
problem". The second type of productivity - which is getting more and more
important - is _across_ projects and codebases. And that gets thrown out of
the window with generics.

I don't know whether generics is a good idea anymore. If there was an option
to undo my vote in the survey...

~~~
jadbox
All I want is typesafe efficient higher order list operations like map,
filter, reduce, flatmap, zip, takeUntil, etc. I don't care if Go puts them
into the stdlib and only allows these operators to use special internal
generics to operate. If Go had a robust higher order std library like this, I
would stop asking for generics. Again, must be typesafe and zero overhead.

~~~
bradfitz
One of the main motivations for adding generics is that we could then put
typesafe algorithms and data structures in the standard library.

~~~
sdegutis
What changed? Why is the Go team finally considering this now, when it has
been requested for these exact reasons by many credible developers since Go's
beginning?

EDIT: Yes, something has changed. They always said "no, but maybe later" and
now it's "ok how about now". That is a big change and I am just asking what's
the reason for it.

~~~
ianlancetaylor
A year ago we said it was time to move toward Go 2:
[https://blog.golang.org/toward-go2](https://blog.golang.org/toward-go2) .
This is another step in that direction.

~~~
sdegutis
Then I'm a year late in asking why the Go team is now deciding to say yes to
something they said no to for over half a decade.

~~~
ianlancetaylor
The blog post explains that.

------
floatingsmoke
A bunch of pioneer project like docker, kubernetes, etcd, prometheus etc. has
been built with go and I don't believe that the maintainers suffered lack of
generics and error handlers. On the other hand, as a new go programmer, I can
really dive into their code base and understand each line of code without
thinking twice. This comes from simplicity.

But these possible nested error handlers and generics will lead developers to
think more than twice during writing or reading a code base. These ideas is
not belong to go era but Java, C++ etc which go doesn't wanted to be like.

Someone here has mentioned that internal support of generics for slices, maps
and other primitives. I think this can be the best solution for generics in
go. For the error handling I think more elegant way could be found.

Please do not rush.

~~~
jasonwatkinspdx
> A bunch of pioneer project like docker, kubernetes, etcd, prometheus etc.
> has been built with go and I don't believe that the maintainers suffered
> lack of generics and error handlers.

Here's an experience report from k8s: [https://medium.com/@arschles/go-
experience-report-generics-i...](https://medium.com/@arschles/go-experience-
report-generics-in-kubernetes-25da87430301)

They've been using a code generator as a work around:
[https://github.com/google/gvisor/tree/master/tools/go_generi...](https://github.com/google/gvisor/tree/master/tools/go_generics)

~~~
Arzh
I think code generators are a better path than having the compiler generator
the code for you. It's nice to look and see what is about to be built instead
of waiting for it to be built then trying to debug with breakpoints.

------
spullara
I really thought they were going to allow their idealism to win over
pragmatism. Versioned modules, generics and exception handlers all going in is
really going to put the defenders of them being missing in a tough spot.

~~~
reificator
I've always been of the opinion that generics would be nice to have. But
people were acting ridiculous about their exclusion. You can write plenty of
useful programs without them.

~~~
pjmlp
Of course we can, after all we were writing code before generics became
mainstream around the mid-2000's.

The point was creating a language without them after they had become a common
language feature across all major static languages.

~~~
randomdata
C doesn't have generics. Objective-C didn't get generics until 2015, which is
well after Go was released. Not all major static languages have generics and
not all major static languages had generics when Go was being planned.

Not that Go ever planned to not have generics. It was always simply a question
of how to implement them in a non-ridiculous way. They were warned early on by
members of the Java team to tread carefully and not make the same mistakes
they did.

The team has crafted many generics proposals over the years, but they all fell
short one way or another.

~~~
pjmlp
C has basic support for generics since 2011.

Objective-C merges C's type system with the dynamism of Smalltalk. Generics
are only meant as a mechanism to ease interop with Swift.

Dynamic languages naturally don't require generics due to their semantics.

The team always handwaved the remarks of many of us as they now themselves
recognise.

"In retrospect, we were biased too much by experience with C++ without
concepts and Java generics. We would have been well-served to spend more time
with CLU and C++ concepts earlier."

------
wvenable
I'm disappointed the Java has given exceptions such a bad wrap that all new
programming languages dance around them like crazy. I understand Rust going
with error returns due to it's low-level nature but Go is pretty high level.

This semi-explicit error handling code optimizes for fairly trivial cases as
very few real-world functions don't have some kind of exception condition. The
difference between prefixing nearly every function with "check" vs. having
that implicit is very small.

Proper use of exceptions puts the focus on where you can _handle_ the error
rather than where the error occurs. Java screws this up with checked
exceptions, which puts the all focus on error site, and programmers have
subsequently determined (correctly) that checked exceptions are hardly better
than error returns.

However, that is fundamentally the wrong way to look at error management. If a
method 17 levels deep on the call stack throws a network error, I shouldn't
have to care about those 17 levels because I'm going to restart the whole
operation at the point I can do that. The important part is not where the
error is raised/returned.

~~~
mike_hearn
It's not really about Java giving exceptions a bad rap. After all exceptions
are a feature of almost any OOP language designed after 1990 and they work
nearly the same way in all of them.

I've argued in the past that the reason the current crop of languages that
compile to native code tend to avoid exceptions, is to do primarily with
implementation cost, the backgrounds of the language designers and a mistaken
attempt at over-generalisation in an attempt to find competitive advantage.

[https://blog.plan99.net/what-s-wrong-with-exceptions-
nothing...](https://blog.plan99.net/what-s-wrong-with-exceptions-nothing-
cee2ed0616)

One problem is that the sort of people who write native toolchains that
compile to LLVM or direct to machine code like Go, tend to be the sort of
people who spent their careers working in C++ at companies that ban C++
exceptions. So they don't really care or miss them much.

Another is complexity. One of the most useful features of exceptions is the
captured stack trace. But implementing this well is hard, because generating a
good stack trace in the presence of optimised code with heavy use of inlining
requires complex deoptimisation metadata and many table lookups to convert the
optimised stack back into something the programmer will recognise. Deopt
metadata is something the compiler needs to produce as it works, at large cost
to the compiler internals, but most compiler toolchains that aren't optimising
JIT compilers (like the JVM) don't produce such metadata at all. So stack
traces in natively compiled programs are frequently useless unless you
compiled in "debug mode" where all optimisations are disabled. VMs like the
JVM have custom compilers that generate sufficient metadata, partly because
they have more use for it - they speculate as they compile and use the
metadata to fall back to slow paths if their speculations turn out to be
wrong. But a language like Go or Rust doesn't have a runtime that does this.

Finally, I find many of the arguments cited against exceptions to be very
poor.

For example the Go designers cite Raymond Chen's blog posts as evidence that
exceptions are bad because it's easy to forget to handle errors and the checks
are "implicit". This seems totally backwards to me.

You cannot forget to handle an exception in most languages that use them. If
you forget a method can throw and don't catch it, the runtime will catch it
for you and print out a stack trace and error message that tells you exactly
what went wrong and where. The thread won't continue past the error point and
the runtime will stop the thread or entire app for you. But if you forget to
check an error code, the error is silently swallowed and no diagnostics are
available at all. The program will continue blindly in what is quite possibly
a corrupted state.

The Go designers argue that with exceptions you can't see "implicit checks".
This is an odd argument because, beyond not being able to see if you are
ignoring a return code, all programs must have implicit checks of correctness.
For example checking for null pointer dereferences or divide by zero
conditions, yet it makes no sense for e.g. divide to be a function that
returns an error code you must check every time. In C, such checks turn into
signals that can be caught or on Windows, the OS delivers them as exceptions!
Yes, even for C programs, it's called SEH and is a part of the OS API - all
programs can have exceptions delivered to them at any point. If you don't
catch them then the OS will catch it for you and display the familiar crash
window. UNIX uses signals which are not as flexible but have similar
semantics; if you don't catch the signal you'll get the OS default crash
handler.

So this is no knock against exceptions. Moreover, very often you don't _want_
to handle an error. Not all code can or should attempt to handle every single
thing that can go wrong at the call site, or even at all. So in practice
errors are almost always propagated back up the stack, often quite far up. For
many errors there's nothing you can do beyond the default exception handling
behaviour anyway of printing an error or displaying a crash dialog and
quitting, e.g. many programs can't handle out of memory or out of disk
conditions and nor would it make sense to invest the time in making them
handle those conditions.

Exceptions were developed for good reasons; they were designed in response to
the many enormous problems C-style error handling created and codified common
patterns, like propagation of an error to the point where it makes sense to
capture it, changing the abstraction level of errors and so on.

The Go designers have realised what many people told them from the start -
that their error handling approach is bad. Unfortunately they don't seem to
have taken the hint and reflected on why they made these bad decisions in the
first place. Instead their language comparison ignores all the other languages
that went a different direction, and only looks at Swift and Rust. Neither
language is especially popular. Other modern languages like Kotlin which _do_
use exceptions are completely ignored.

In conclusion, nothing in this document makes me think that Go is likely to
fix its self-admitted design errors. They're probably just going to make
variants of the same mistakes.

~~~
wvenable
> It's not really about Java giving exceptions a bad rap. After all exceptions
> are a feature of almost any OOP language designed after 1990 and they work
> nearly the same way in all of them.

Java has checked exceptions which have been universally agreed upon as bad
idea. No subsequent languages use them and they've even been removed from C++.
Unfortunately most common first programmers' language is Java and they learn
the worst possible implementation of exceptions. All your pro-exception
arguments (and all mine) pretty much all apart when dealing with checked
exceptions. So it's easy to see why anyone who's only exposure to exceptions
was from Java would consider error returns the superior option.

> But implementing this well is hard, because generating a good stack trace in
> the presence of optimised code with heavy use of inlining requires complex
> deoptimisation metadata and many table lookups to convert the optimised
> stack back into something the programmer will recognise.

Of course, the only reason any of this is necessary is because compilers don't
implement exceptions in naive way of simply returning and propagating the
exception the same way you would propagate an error return. Instead they go
through a convoluted mess to ensure that in the case the exception doesn't
occur, you pay no run-time performance penalty. So, in fact, this is still an
advantage of exceptions over error returns. This is merely an optimization
that is possible when error handling is not explicit.

~~~
mike_hearn
That's all true. I've become more sympathetic to checked exceptions over time
though. I think the issues Java had with it are more to do with poor choice of
what to make checked in the standard library. For example IOException should
not have been made checked. Also, it's too difficult to change an exception
from checked to unchecked, and it should have created suppressable warnings
rather than compiler errors.

It's a pattern - early versions of Java erred too much in favour of making
things compile errors rather than warnings, like unreachable code being an
error , even though it's common whilst debugging or writing code (Go made the
same mistake). Not catching checked exceptions is something very common whilst
making prototype code or simple command line tools where all you can do with
most errors is print the message and quit anyway. It isn't worth forcing the
developer's hand via an error.

But I won't be surprised to see some variant of checked exceptions be explored
again in the coming years, probably with IDE support. Being able to know how a
method can fail in the type system, beyond just checking the docs, is a very
useful thing if the information is used in a reasonable way.

~~~
wvenable
Checked exceptions fundamentally violate most of the principles of object-
oriented programming by forcing you to expose implementation details into the
method type signature. Encapsulation, abstraction, and even polymorphism are
violated by this.

Java programmers typically get around this by creating _ClassName_ Exception
classes and then stuff the real exception (untyped) into the innerException
property.

Checked exceptions are conceptually and fundamentally flawed.

~~~
dragonwriter
> Checked exceptions fundamentally violate most of the principles of object-
> oriented programming by forcing you to expose implementation details into
> the method type signature

No, they don't. They can be abused in a way which does this (and can be
implemented in a way which unnecessarily creates avenues for it), but checked
exceptions in general are isomorphic to static return types, and don't violate
OOP any more than static return types do.

~~~
wvenable
Static return types would violated OOP as well if they exposed implementation
details unrelated to the result of the operation.

If you have a method called GetPrice() that uses a database, you know it,
because you have to expose all the possible exceptions. If you change that to
use a file or a network service, you have to change every caller and every
caller of that. Is that abstraction? No. Is it polymorphism, also no.

You can't have two different implementations of GetPrice() that are compatible
unless they have exactly the same exception result. What's the value of that?

~~~
dnomad
> Static return types would violated OOP as well if they exposed
> implementation details unrelated to the result of the operation.

You're arguing against a particular usage of checked exceptions. You don't
have to use checked exceptions like that so the argument is not convincing.

> If you have a method called GetPrice() that uses a database, you know it,
> because you have to expose all the possible exceptions.

And if you had a method called getPrice() that should always fail if a product
is part of a bundle and cannot be purchase separately then you _would_
potentially want every caller to be aware of that possibility. One error
condition is an implementation detail and the other is a fundamental invariant
of the domain. Guess which one checked exceptions should be used for?

> You can't have two different implementations of GetPrice() that are
> compatible unless they have exactly the same exception result. What's the
> value of that?

That's the whole point. There are invariants that should be communicated in a
type-safe manner. The confusion here isn't new and is actually well
understood. _All_ error signals fall into two buckets -- does my caller care
and can she respond sensibly to it or will she just pass on the error and/or
quit. The benefit of checked exceptions is maximum flexibility: you can guess
(and it's always a guess) that your caller doesn't care and throw an unchecked
exception or, if you feel really strongly that the caller should care because
some key invariant has been broken, you can throw a checked exception. It's
not an exact science but this question -- does my caller care? -- goes to the
very heart of encapsulation and abstraction.

~~~
wvenable
I disagree. If you have an polymorphic getPrice() as part of an interface than
you can't possibly list all the invariants as individual well-typed
exceptions. That would assume perfect knowledge of all possible
implementations of that method now and in the future. That's not OOP.

In a non-polymorphic case of a bundled item, you'd simply not have a
getPrice() method on the item at all. It might simply not implement the
PricedItem interface, for example. There are plenty of ways of ensuring all
invariants are met, in a type-safe way, without using exceptions. If it's
possible to come to situation as normal correct operation (such as an item in
bundle) then it's not exceptional.

------
stephen
Well, shoot. I don't like/don't want to like Go, but they keep getting better.

My general annoyance is that they keep things ~just different enough to be
irksome, e.g. in this proposal generics is using parens:

type List(type T) []T

Instead of using <> like every other language (okay, I meant Java and C++)
with generics:

type List<type T> []T

(Another example of being ~just different enough to be annoying was switching
from C style `int i` to `i int` but then not using colons like `i: int`. And
then using type name case for public/private (wtf), so I read variable decls
of `foo foo`, which happens all the time in methods that are private
implementation details, and my polygot brain interrupts: ...damn, which one is
the type again?)

As usual, I'm sure `(type T)` vs `<type T>` will be due to "reasons" (...oh,
maybe it's that crazy `<-chan` syntax that I dyslexically can never keep
straight and wish it was just real type names like `ReadChan[T]` and
`WriteChan[T]`), but really I hop languages all day and this makes a big
difference to my eyes going back/forth.

They should also go back and fix `map[K]V` to be `Map[K, V]`; iirc one of the
touted benefits of `gofmt` was to "make automated source code analysis/fixing
easy". Great, take advantage of that and get rid of that inconsistency. Same
thing with `Chan[string]` instead of `chan string` (both are public types so
should be upper case for consistency). It's a major release, just do it.

Anyway, disclaimer yeah I'm nit picking and this is what made Guido quit
Python. To their credit, Go has great tooling, the go mod in 1.11 finally
looks sane, etc. Their continual improvement is impressive.

~~~
enneff
The proposed contracts design is quite different to Java and C++'s generics.
Why should they use the same syntax?

If you want Java and C++ then they are there for you to use. Go is a different
language and its differences are not gratuitous. Use it for a while and you'll
find out why "i int" works better than "int i" (compare Go's pointer and
function type specs to C's).

~~~
stephen
> Why should they use the same syntax? ... Go is a different language

True; I harp on Go primarily due to their IMO idiosyncratic approach: AFAIU
they claim to be a "purposefully boring", "good for in-the-large" language.
(Which is great, I want/like languages like that.)

But then they do cute things like the upper/lower case for private, the
"method params can be defined `a, b int` because it saves an `int`", the `:=`
assignment vs. a standalone `var`/`val` for "hey this is a new variable", not
throwing in a ":" for `i: int`, etc., that are all, IMO, very
tangential/distracting from their core mission of being a "boring language".

So, if they were purposefully trying to be a wildly different language, sure,
that's great, I would care a lot less.

But it's that they want to be a boring language, and yet are "off" by just
enough, that it admittedly frustrates me more than it should.

~~~
enneff
Those things aren't being "cute". For example, upper/lower case for
exported/unexported is a huge readability benefit.

You would be less frustrated if you took the time to understand why these
decisions were made, instead of pronouncing them arbitrary.

Go's designers were long-time C users (longer than anyone else in the world,
in fact); they had every reason to stick with the way things worked in C, but
they deliberately chose to make Go different.

~~~
jzoch
uppercase vs lowercase is not a readability benefit. For someone reading the
code an explicit 'pub' or other operator is much more readable, as in "I
understand the implications of what I am reading". Many people can skim over
it without realizing the difference of the var name case.

~~~
infogulch
I thought it was weird for 2 months. Hasn't bothered me in the 4 years since.
Except [de]serialization at first, and then I got over it when I realized it's
worth the verbosity just to be able to flexibly rename things internally while
the external interface remains stable.

------
dagss
I am disappointed that go seems to follow Java and C++ with generics support
instead of taking a more LISP-like turn like e.g. Julia.

I would much rather have powerful hygienic compile-time macros to generate
code. Then I would have the power to do all thay generics does but also a lot
more.

Just having those powers at compile time evaluation would be rather good,
don't need to go all the way to Julia/Lisp.

We know what mess C++ became, with people abusing the template system to do
compile-time metaprogramming. Much better to simply provide a good macro
system.

~~~
gameswithgo
generics are immune to much of the mess that templates cause. It is a somewhat
different thing. They are also less powerful, but that is fitting with Go's
desire to avoid language constructs which let people make really confusing
code, I think.

If you want cool meta programming abilities, there is Rust, Nim, Lisp, Julia,
F# and others to choose from. No need for Go.

~~~
IshKebab
Templates _are_ generics.

~~~
int_19h
They're in that weird middle ground between macros and generics, but arguably
still closer to macros. The original pre-Standard C++ templates were pure
macros. Then two-phase lookup was added in C++98, which muddied the water.

------
ainar-g
What about sum types? During 4 years I've been programming Go professionally I
missed sum types more times than I missed generics. Writing yet another

    
    
      interface SumType {
          isSumType()
      }
    

won't kill me, but it does get annoying.

~~~
arianvanp
for sure. It would also be lovely to add sum types to Protobufs too, while
we're at it. many message protocols are very neatly described with sum types
in my opinion.

Also, errors would be way more elegant as a sum type:

instead of:

err, a := x

if err != nil { }

we can do:

switch a { Err(err) -> {}, Ok(a) -> {}, }

and avoid null pointers :) and then "check" is literally just "bind/ try!,
flatMap, =<<" or however your language du jour calls it :)

~~~
mayoff
Regarding Protobufs, did you mean something other than `oneof`?

[https://developers.google.com/protocol-
buffers/docs/proto#on...](https://developers.google.com/protocol-
buffers/docs/proto#oneof)

------
aleksi
Plus a short video in the Go blog:
[https://blog.golang.org/go2draft](https://blog.golang.org/go2draft)

------
tmaly
> The call with type arguments can be omitted, leaving only the call with
> values, when the necessary type arguments can be inferred from the value

I think if too many different ways are offered in the generics proposal, it
will work against the simplicity of the language for newcomers.

~~~
TACIXAT
Yea, I really like Go because it isn't C++2049. I was able to get writing it
quickly, and I don't have to think too hard about what the right (read: most
clever) way to implement something is given all the language features. I like
to just write code and I think a post here said it best, Go gets out of the
way and lets you build.

~~~
scns
weeks of work can save an hour of planning

------
_ph_
I am very excited to see the draft designs. They point to some good progress
with some open design aspects with Go.

The error handling concept looks to me as if it is ready for implementation in
one of the next Go revisions. The fundamental of Go error handling vs. an
exception mechanism always was, that Go errors are returned as part of
function results. The calling function gets the result and the error and then
can decide how to deal with it - handle the error, or return from itself.
Exceptions not only mean automatic returns, but also automatic stack unwinding
across all functions that do not implement an exception handler. I have always
preferred principle of the Go error handling to exceptions.

What is great about the error handling proposal is, that it is fully contained
inside each function. It does not change the signature of functions, they
still return an error amongst the return values or not. You cannot see when
looking at a function, whether they use the proposal or not. But like the
defer statement, they offer an elegant way to schedule effects to happen, when
a function returns. A plain return will trigger all deferred function calls, a
check "catching" and error will trigger all handlers and then all deferred
functions. Furthermore check works as a filter, so chaining functions which
return values and errors becomes much easier. I also like the choice of
"check" vs. "try" or "?". A short word reads better than an operator character
and try is too familiar with an exception mechanism, what check/handler is
not.

On the first glance, the handlers looked a little bit confusing about the
logic flow. But if one considers check/handler the twins to return/defer, they
become very obvious and fit very well into the existing framework. Handler can
be considered a deferred function for the return in the error case, and check
as triggering that error case return.

On the generics, the concept is not finished yet, but what is there so far
looks like to be on a good path to adding generic types to Go without making
it a much more complex language or too many sacrifices like the type erasure
in Java. On the syntax side, I like that they managed to find a syntax, which
uses the normal parens used for function and method syntax before, as this
makes the code a bit nicer to read. I am very curious how they develop.

------
adamwk
One nit-pick in the generics overview. It claims Swift introduced generics in
Swift 4, but parametric polymorphism has been in the language since Swift 1,
though refined after each subsequent release.

~~~
slavapestov
This is also incorrect:

> Equatable appears to be a built-in in Swift, not possible to define
> otherwise.

Equatable is a standard library type.

~~~
dbaupp
They may trying to refer to the automatic synthesis of Equatable conformance.

------
quotemstr
FWIW, these changes make it much more likely that I would voluntarily choose
to write a Go program.

~~~
felipeccastro
I agree, honestly the error handling alone has always been the main reason I
never used Go. Yes, I knew there were good reasons for that design, but I
prefer to have options in how to handle errors and keep a clean, elegant code.

------
djhworld
Really excited to see things start to take shape, I'd imagine there will be a
lot of rounds in that feedback loop to get it right, but glad to see the Go
team have stuff on the roadmap

------
lostmyoldone
That error handling looks very much FP inspired, intended or not.

I'm not going to say the M-word, someone will maybe say it in ... either case.

~~~
wisam

       I'm not going to say the M-word, someone will *maybe* say it in ... *either* case.
    

Just wanted to say I see what you did there. Nothing insightful to add.

~~~
lostmyoldone
Had a bit of a rough day, and insightful or not, your comment still did
brighten my day.

------
incompatible
It seems to be lacking a mechanism for accumulating multiple errors, which can
be done using a package like hashicorp/go-multierror.

The usage pattern is that you have a complex function that does something like
parse a document and return the data from it. But the file format is so
complex, that a lot of minor problems can occur, but they don't necessarily
prevent you getting some data out. So the return value is the data and a set
of errors (or warnings, if you like).

~~~
ainar-g
This might actually be addressed, albeit very quickly, in one of drafts:
[https://go.googlesource.com/proposal/+/master/design/go2draf...](https://go.googlesource.com/proposal/+/master/design/go2draft-
error-printing.md#error-trees).

>To implement formatting a tree of errors, an error list type could print
itself as a new error chain, returning this single error with the entire chain
as detail. Error list types occur fairly frequently, so it may be beneficial
to standardize on an error list type to ensure consistency.

------
fdr
Heh, it's hard to get someone more qualified to look at this issue than Ian
Lance Taylor. An unknown member (by most) of the Go team that more ought to
know about.

------
the_grue
(I usually avoid ranting, but just couldn't resist it this time around. Go on,
bring on your downvotes, you still know deep inside this is the truth.)

Oblig. generic laughter [1]

Are Go's problems over, then? I have read the error handling draft so far, and
girl, was I in for a big surprise. Imagine a few nested try-catch blocks,
where all the try{} and catch{} are written... imperatively, one after the
other, with both try{} bodies and catch{} blocks interspersed in the same
scope. E.g., line 1: "catch" block, lines 2-10: "call IO" statements, line 11:
another "catch" block, etc. The errors are always passed to the previous
"catch" block, then the one before that, etc, until the error has been
handled.

Haven't we seen something like this before? Ah yes, the goto statements!

This was designed by a person with a severe case of scope-o-phobia. If that
person has designed the rest of Go (and I think she has), the language might
be unsalvageable. Just start from scratch, and do it right this time around.
Oh wait, no need - we already have Kotlin.

/rant

[1]
[https://www.youtube.com/watch?v=iYVO5bUFww0](https://www.youtube.com/watch?v=iYVO5bUFww0)

------
s_kilk
It'll be interesting to see how Go handles a new major version. Will it be a
Python 3 situation? Or more like, I dunno, Ruby 2 and onwards.

~~~
yashap
I think the two big problems with the Python 3 rollout were a lack of
compelling reasons to upgrade (at the time - many big features have been
implemented in Python 2 in the years since), and upgrades like this being
inherently scarier in dynamically types languages.

If Go 2 brings improved error handling and generics, those are HUGE reasons
for the community to get onboard and upgrade quickly. Plus with compiled
languages, the upgrade is way less scary - so many errors will be caught at
compile time that can sneak through as subtle bugs in dynamically types
languages (unless there’s GREAT test coverage). Also, Go 2 may not even bring
breaking changes! Though I’d imagine some things will still break no matter
what (code analysis tools, programs that use variable names that become
keywords, etc.).

Maybe I’m being naive, but I think Go 2 will be adopted quickly with
relatively little pain, as long as it brings super-desired features like
genetics and error handling improvements.

~~~
reggieband
> I think the two big problems with the Python 3 rollout

When Python 3 was announced I thought "this is a great way to rollout". After
a few years I thought "what a terrible rollout, even I haven't changed to
version 3 100% yet because of some libraries I rely on are still only verion
2". Now I'm back on the positive side of things. It turns out, in the grand
scheme of things, 5+ years for a new version to be adopted didn't really
affect me in a negative way. I'm back on the "slow and steady wins the race"
bandwagon.

~~~
nordsieck
There's a bit of survivorship bias there. There was a very real chance that
Python would get replaced by another language in the same way that Ruby and
Python replaced Perl.

If that had happened, you'd probably still be in the "what a horrible rollout"
frame of mind.

~~~
antod
I seem to remember that Ruby was already replacing Python pretty heavily 10
years ago. Rails was gaining momentum at a huge rate and Python web frameworks
were dying, tools like Puppet were on the rise etc. The full history of Perl 6
still lay ahead too.

If it hadn't have worked out in the end, it would be just as relevant to claim
"what a horrible rollout" was just the benefit of hindsight talking.

I think the Python devs and community need to take some credit for managing to
pull off that transition successfully (even if it took longer than most
expected). And to claw their way back into relevance again.

And every other language community now has a case study they reference during
discussions about language upgrades :)

------
codr4
I have nothing more substantial than a gut feeling to back this up, but it all
feels half baked and too conservative to me. Like they cranked out some
special syntax and new concepts that sort of solve their problems and called
it a day. It may look like the path of least resistance initially, but sooner
or later the features will start stepping on each others toes.

~~~
enneff
> it all feels half baked and too conservative to me

Your gut is wrong. These proposals have been in development for years. The
author of the generics proposal has been thinking about the problem for nearly
a decade.

~~~
codr4
Sorry about the toes, but identifying with a programming language will always
get you into trouble. My gut is fine, thank you very much.

They're C heads, trying to build a simplified C; at least they were, they sort
of messed that up with garbage collection, wild west threading and using
reflection for all the wrong reasons.

Sure, now the tone is different. But back in the days when the language was
first released, they would make fun of anyone mentioning generics or
exceptions.

------
marcrosoft
To the Go authors:

* I've never seen a language rise in popularity as fast as Go.

* Go is doing something right.

* Please refrain from changing the language.

------
eximius
You could _almost_ write a `check!` macro for rust that would have the same
semantics. You can obviously already write one when no `handle` exists (it'd
just be `try!` or `?`), but I can't think of how you'd build up the handles
like they describe without a procedural macro.

You likely could do something semantically equivalent with procedural macros.

`handle!{ block }` would define a block to inline later when needed and then
`check!` would do the normal thing but also inline that block, just like Go's.

Also, I feel like it is a little disingenuous to say that Rust has `no
equivalent of handle` when it has something so similar in
`Result::or_else(self, Op)`. You don't get the handle accumulation and you
have to pass in a closure at the site, but they're remarkably similar.

------
sukunrt
A question on generics from the docs

if the declaration is

func Keys(type K, V)(m map[K]V) []K

why do we need to call it like

keys := Keys(int, string)(map[int]string{1:"one", 2: "two"})

can't the types int and string be inferred from

Keys(map[int]string{1:"one", 2:"two"})

~~~
ainar-g
They are. Further in the draft there is an example:

    
    
      var x []int
      total := Sum(x) // shorthand for Sum(int)(x)

~~~
sukunrt
ah crap. how did I miss that! Thanks!

------
pjmlp
> In retrospect, we were biased too much by experience with C++ without
> concepts and Java generics. We would have been well-served to spend more
> time with CLU and C++ concepts earlier.

Yep!

The overall design looks quite good, though.

------
teolandon
I do like the idea of contracts for generics, seems to fit in well with Go's
interfaces and whole design.

What I do not like at all is that type arguments are put in parens, same as
normal arguments. Something like Java's <T> would be more readable IMO. I have
to do a double-take to realize that the first paren is for the type arguments.
Combine that with the parens that are used for receivers and multiple return
values, and function signatures become paren hell.

~~~
platinumrad
<T> has issues with ambiguous parsing as < and > are also the less than and
greater than operators. Rust recently had an small issue with this:

[https://github.com/rust-lang/rust/pull/53562](https://github.com/rust-
lang/rust/pull/53562)

~~~
edflsafoiewq
[T] has precedents (Eiffel comes to mind).

~~~
ianlancetaylor
Unfortunately [T] is also ambiguous with the current Go syntax. The parser
can't distinguish an array declaration from a generic type.

~~~
alecthomas
How about the following:

    
    
      contract Addable(t T) {
      	t + t
      }
    
      func Sum.<T Addable>(x []T) T {
    	var total T
    	for _, v := range x {
    		total += v
    	}
    	return total
      }
    

It has the nice property that it's somewhat analogous to type conversions, the
grammar should be unambiguous, and it's much less visually confusing than re-
using parentheses IMO.

------
munificent
This is very exciting!

One challenge with introducing generics after the language has shipped is that
the language was already designed around their absence. In particular, there
are a number of "generic" or "polymorphic" that are built into the language.
In order to handle them specially, they often have special-purpose syntax.
Examples (note: I'm not very familiar with Go, so I might have some of this
wrong):

* Addition, which is overloaded for various number types, uses "+".

* Accessing array and map elements uses "[...]".

* Equality, which is overloaded for various types, uses "==".

Having dedicated syntax for these makes sense in Go 1 because they are
special. They do things user code cannot do. Prohibiting operator overloading
reinforces this. If you see a "+" in code, you know it's the magical built-in
"+" operator.

But with generics (and potentially overloading), these operations no longer
need special powers. It's possible to write your own collection type with a
type-parametric "subscript operator". You just don't get to use the "[...]"
syntax.

This is particularly problematic because it interacts poorly with contracts,
which specify _syntactically_ which operations are used. Most of the contract
examples in the doc here use "==", "+", and "[...]".

So even though you have generics and constraints (contracts), it's very hard
to define a generic function that works with both built-in generic types
(slice, map, etc.) and user-defined ones.

The end result is probably going to be something similar to Java where the
"best practice" is to use named methods for all operations ("Add()",
"Equal()", etc.) and then provide wrappers that lift the built-in types into
this more flexible world like you do with ArrayList instead of raw arrays in
Java.

That sucks because the built-in syntax is better. It's inane that Java has
array literals, a nice subscript operator, and a terse ".length" property but
99% of Java code doesn't get to use any of them when working with "arrays".
Instead of:

    
    
        array[0] = array[array.length - 1];
    

It's all:

    
    
        array.set(0, array.get(array.size() - 1));
    

I could certainly be wrong, but it seems Go is on that same path.

Aside from this, the generics proposal looks really neat to me. The
limitations on methods will also likely be very painful, but it's a hard
problem with no easy solution given Go's approach to structural typing,
methods, and the desire to not require runtime specialization. Language design
is hard. Language evolution is 10x harder.

------
kodablah
I was hoping the generic type would be part of the struct/interface/func
declaration section instead of the name part of the declaration. I wonder how
that will work with anon ifaces/structs/funcs? I assume just a type param list
before the anon decl? (seems easy with funcs, less so w/ types).

Also, I assume I can make named aliases of types with concrete type params
values?

~~~
sseth
There are examples of aliases e.g.

type VectorInt = Vector(int)

------
norswap
The case against exceptions is unfair to Java, whose model is CHECKED
exceptions. Of course, people hate them, but they are semantically equivalent
to the new proposed solution, which is itself much better than current error
handling in Go, which is frankly horrid.

I must admit the check/handle mechanism is syntactically more pleasant though,
avoiding surrounding code with try/catch.

------
atombender
I'd like to see sum types (often called product types, union types or enums)
introduced. I think they would fit Go well, and get us away from the current
awkward "interface hell".

For example, imagine you're implementing an AST. You might have something
like:

    
    
      type Identifier struct {
        Name string
      }
      type StringLiteral struct {
        Value string
      }
    

and so on. Currently the only way to unify them is with a dummy interface:

    
    
      type Expression interface {
        isExpression()
      }
    

and then you have:

    
    
      type (Identifier) isExpression()
      type (StringLiteral) isExpression()
      // etc.
    

Then in your compiler you'll have lot of switch statements:

    
    
      switch e := expr.(type) {
        case Identifier:
          // ...
        case StringLiteral:
          // ...
        default:
          panic("unexpected expression")
      }
    

There are multiple problems here.

One, unifying _pure data_ structs with an interface is not what Go interfaces
were intended for, and is pure type system abuse. Fine, so maybe in this case
Expression also gets things like GetPos() and what not, which sort of
justifies an interface. But there are plenty of use cases where there are _no_
methods.

The other problem is that there's no way to prove at compile-time that a
switch is exhaustive. Add another expression struct, and all your switches
still compile. Someone made a post-processing tool (go-sumtype), but it's an
awkward and buggy hack that has to be run as a "go generate" task next to your
development workflow.

Thirdly, performance-wise, this forces every struct to be boxed in an
interface.

I'd rather like to be able to declare something like:

    
    
      type Expression (Identifier || StringLiteral)
    

It can, I suspect, be simpler than Rust's full-blown sum type support, because
you're essentially declaring a new type that holds the possible union of
types.

Of course, this could be elegantly extended to errors:

    
    
      func Open(fn string) (*File || error)
    

The downside to this, as opposed to something like Rust's Result, is that you
can't add methods, so you can't chain these calls without something more.

On the other hand, it would work together with the proposed check/handle
mechanism.

------
JulianMorrison
I don't recommend any attempt to unify check/handle and panic/defer/recover.
They don't behave the same because they aren't doing the same: one is
controlled handling of normal errors with reduced boilerplate, the other is
"crashing with cleanup".

------
IshKebab
> A more robust version with more helpful errors would be....

My issue with that example is that it isn't like that. People don't use
fmt.Errorf() with exactly the same string on every line. They like to add per-
line context.

Then you're back to one handle block for every line and it's even worse.

------
kanishkdudeja
This made my day. I'm so happy. Going to spend the week going over the draft
proposals!

------
otabdeveloper2
We need a variant of Greenspun's tenth rule for systems programming languages.

Any sufficiently old systems programming language that aims for simplicity and
clean code will eventually devolve into a variant of C++, except ad-hoc and
poorly specified.

~~~
scns
What the Go team is doing seems diametrically opposed to "ad-hoc" to me.

------
Attained
Really wish they'd consider looking at some of the more fun things TypeScript
types can do. Like specifying a return type as being Pick<SomeType, 'x' |
'y'>. Imagine this in combination with Go interfaces.

------
thejerz
I've never understood why Go tries so hard to reinvent exceptions. I've read
their manifesto, their books, and this new exception syntax, but I'm not
convinced the problem they're describing exists; and, even if it does exist,
that their implementation solves it.

Exceptions in Ruby, Python, Java, C#, etc. are a tried-and-true paradigm for
handling unexpected code paths, with a clear syntax: the only problem with
exceptions is that developers don't use them, or don't use them properly, or
aren't forced to use them (e.g, checked exceptions, a la Java's throws), all
of which can be fixed with compilation rules.

As someone who writes defensive code, and utilizes a lot of exception
throwing/handling for 14+ years, Go's lack of a rational exception framework
is a big turn off.

~~~
jimmy1
> a tried-and-true paradigm for handling unexpected code paths, with a clear
> syntax

Citation needed here. Who seriously has this opinion? There was just a
Java/JVM conference on the very topic of how to make exception handling
better.

Tried? Yes.

True with a clear syntax? Definitely not.

------
skybrian
This is just syntax, but I'm curious about the decision to go with regular
parens for supplying type parameters, rather than the familiar angle brackets,
or perhaps Julia's choice of curly brackets?

~~~
yiyus
Read the document:
[https://go.googlesource.com/proposal/+/master/design/go2draf...](https://go.googlesource.com/proposal/+/master/design/go2draft-
contracts.md#why-not-use-like-c_and-java)

------
ilovecaching
I don't understand why promoting Go to be the next Java is a good thing. Let's
just use it to do what it's good at, and if you need generics, go use a
language that has generics. We already know what a language that can do
everything looks like, and it's a mess.

Plus, are they going to rewrite the entire STL? Are we going to get a library
of Algos and DS when we have generics? Generics pretty much changes the entire
look and feel of the language. Might as well make it a fork than an iteration.

So much for simplicity and copying. :) RIP Go 2009-2019

~~~
jagger27
The Go team is pretty set on maintaining backwards compatibility—rewriting the
standard library is the exact opposite of that. Just because the promise is Go
1.X won't break your code doesn't mean they don't remember Python 3.

Besides, which parts of the existing standard library would even benefit from
generics themselves? Of course the obvious thing is to /add/ a set of standard
container templates but that doesn't really step on anyone's toes.

------
dakom
What sorts of things can you do in the Haskell type system that you would not
be able to do with the Go generics proposal?

~~~
edflsafoiewq
Higher-kinded types? Monads, etc.

~~~
dakom
Couldn't one define Monads, etc. with interfaces, once they allow generics?

(there's nothing in Haskell which enforces the category laws in the
typesystem, afaik)

------
chmike
What about literal parameter for functions or types ? I'm just asking because
c++ and D support it.

~~~
ianlancetaylor
One step at a time. It's certainly a natural extension, and I don't think
anything in the current design draft precludes it.

------
pietherr
Go 2 considered harmful

------
ivanjaros
After a year with Go I have to say that I am quite comfortable with it. The
beginning was an absolute hell but once you open up your mind a bit and accept
that it is what it it and if you want to use it YOU have to accept its
concepts. And time heals all wounds :D

From those three points - the error handling seems a waste of time to even
bother with implementing. The way it works now is fine.

The error values - yes, there should be something done. As mentioned in the
draft, you often need information on where the error occurred(func, line,
file) since the error can travel in between libraries, you have dynamic
messages so you cannot declare global variable to compare the error with based
on string content and lastly, error code would be nice too(severity/http
status code).

Generics - I have to say this is pain for most people, I got used to work
around it via wrappers and so far I'm good. I mean, if you have argument as
interface{} and you accept bool or int then why don't you just have Value{b
bool, i int} struct and just check which one it is and proceed with the logic?

I have typed data with 23 different types and a proxy that implements all
interfaces with GetType, SetType and AsType methods and yes, it is long code
to write for something so trivial but in the end it works and I can now go on
with my life. Internally, gRPC does the same when it generates Go client.

To me, one of the things I miss the most is ternary operator, that would save
a ton of code. And I would also welcome removal of nil maps. var foo []string
can be used with append but var foo map[string]string cannot be used as
foo["one"]"two" because you have to foo := map[string]string{} first which
means you have do add 4 lines of unnecessary, imho, code everywhere you work
with maps to avoid panic due to map being nil and not just empty.

~~~
ivanjaros
Also they could implement ``` to allow ` in strings

------
21
This is tragedy. We'll no longer be able to say "lol, no generics" :(

------
tomc1985
Yay, more tech churn...

------
sustain168
when can Go Lang get rid of the address reference(*) shit?

checkout java and python.

------
valarauca1
Generics are just syntax sugar around `interface{}` [1] well hopefully the
runtime JIT optimizes things for me.

I'm rather impressed that when `go` actually implements something I wanted
they still manage to implement it in a way that is hacky and has massive
trade-off's while literally avoiding doing the bare minimum to improve the
compiler. Its disappointing.

[1]
[https://go.googlesource.com/proposal/+/master/design/go2draf...](https://go.googlesource.com/proposal/+/master/design/go2draft-
contracts.md) go to efficiency

