
Go says Wat - beliu
https://about.sourcegraph.com/go/gophercon-2018-go-says-wat/
======
sacado2
Hmm, the first few WATs are pretty bad examples. Any developer, gopher or not,
should know that, when you reassign the value of a function parameter, the
variable when the function returns still has its original value. It's pass-by-
reference 101. Now, sure, slices and the way append work are a bit complicated
to grasp at first, especially if you never used C, but this is not a WAT.

A lot of WATs are more bad practice than problems in the language, IMO (like
WAT 8, modifying the return value twice in deferred functions, with the order
being significant: who the hell writes such a code?)

A few ones, though, really are problematic. For instance I've been bitten more
than once by variable shadowing, especially with error values. IMO, this is
the weakest point of Go.

~~~
jjnoakes
> It's pass-by-reference 101.

Is it though? Slices seem to be passed by value, where the value is a pointer
and some bookkeeping information (like a C struct of (data, len, capacity)).

Some operations manipulate just data (which is visible in the caller, since
that 1/3 of the "struct" was a pointer passed by value) and some operations
manipulate len and/or capacity (which is not visible in the caller, since that
2/3 of the "struct" was also passed by value).

Seems more complex than just hand-waving the details away with "pass-by-
reference 101".

~~~
lann
An illustation of append being confusing:
[https://play.golang.org/p/WGwp2cM6E1B](https://play.golang.org/p/WGwp2cM6E1B)

~~~
scarejunba
This would have been a way better WAT.

~~~
adwhit
Indeed, less 'confusing' and more 'broken'.

------
krylon
"The order of iteration for a Go map (a hashmap) is unspecified, by design."

I that surprising behavior? It is not only well-documented, but is in line
with most other languages that offer a hash table / map / dictionary /
whatever in the base language or standard library.

~~~
miranda_rights
The Go runtime explicitly randomizes the iteration of map, because the
language designers noticed 'Programmers had begun to rely on the stable
iteration order'[0]. Most languages, as far as I can tell, tell you that the
iteration order is undefined but frequently have some stable iteration order
that's consistent for the compiler or the system architecture, and don't take
the extra step of randomization.

[0][https://blog.golang.org/go-maps-in-action](https://blog.golang.org/go-
maps-in-action) \- Header: Iteration Order

~~~
wwweston
Having done the work of observing langugage users apparently want an ordered
map, were there any steps taken to add such a facility, or did efforts stop at
letting developers know they were they were wrong by taking away a de facto
feature?

~~~
likpok
Ordered maps require other tradeoffs. For example, c++ has both: std::map and
std::unordered_map. std::map is a treemap, and so has logn access/insertion
times. It also does a ton of small allocations, and scatters your data across
memory, leading to poor cache performance at scale.

But it has ordered output, and doesn't invalidate iterators on insertion
(hashmaps might, because they sometimes need to rehash).

Generally, the suggestion that I've heard is to avoid using std::map unless
you really really what it specifically provides, because it's expensive and
it's hard to know if you can safely relax those constraints.

~~~
masklinn
> Ordered maps require other tradeoffs.

Ordered maps don't require trees, and don't have that high a tradeoff:
[https://morepypy.blogspot.com/2015/01/faster-more-memory-
eff...](https://morepypy.blogspot.com/2015/01/faster-more-memory-efficient-
and-more.html) [https://github.com/rust-
lang/rust/pull/45282#issuecomment-33...](https://github.com/rust-
lang/rust/pull/45282#issuecomment-336700260)

~~~
fanf2
C++ ordered maps are in sorted order whereas php-style hashes are in insertion
order (recent versions of Ruby and Python have adopted the php semantics; in
JS maps are php style but JS objects are unordered)

~~~
demurgos
Small note, the JS objects are in fact ordered. The lack of determinism caused
issues between browsers so the spec now defines the following order:

1\. Integer indexes (string keys) in ascending numeric order

2\. Other string keys (non-integer indexes) in insertion order

3\. Symbols in insertion order

[https://www.ecma-international.org/ecma-262/#sec-ordinary-
ob...](https://www.ecma-international.org/ecma-262/#sec-ordinary-object-
internal-methods-and-internal-slots-ownpropertykeys)

------
blixt
I was really expecting the slice WATs to carry on to talk about what I
consider a real WAT: you have to perform robust checks on your input slice's
capacity and length if you use append in a function, because append makes its
own choice whether the array of a slice is reused or copied.

Here's a basic demonstration:

[https://play.golang.org/p/8zMmPwtjcxG](https://play.golang.org/p/8zMmPwtjcxG)

In particular, note how this behavior can easily be forgotten when the
semantics are hidden through variadic arguments that are passed an existing
slice (instead of the automatically created one when you pass in actual
variadic arguments).

~~~
masklinn
> I was really expecting the slice WATs to carry on to talk about what I
> consider a real WAT: you have to perform robust checks on your input slice's
> capacity and length if you use append in a function, because append makes
> its own choice whether the array of a slice is reused or copied.

At a fundamental level, the issue is that Go's design decisions make any
modification of a shared slice dangerous, and the language provides no way to
mitigate it.

A really fun one is divergent appends to slices with a shared backing array
and enough capacity for an in-place append:
[https://play.golang.org/p/ZHWo3bFOR0X](https://play.golang.org/p/ZHWo3bFOR0X)

~~~
blixt
That's the one I show in my example above :) The final example shows how
paired with variadic arguments very "weird" things can happen since your slice
may come from an entirely different codebase and you may get very difficult to
debug results.

------
schmichael
I would add WAT 2.5:
[https://play.golang.org/p/gwcHh4-QhHK](https://play.golang.org/p/gwcHh4-QhHK)

    
    
      func main() {
      	x := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
      	fmt.Println("orig:   ", x)
      	mutate(x)
      	fmt.Println("append: ", x)
      	mutate(x[:4])
       	fmt.Println("sliced: ", x)
      }
    

Appending to the end of the slice creates a new slice and therefore the caller
doesn't see the mutation.

However appending to a slice-of-the-slice leaves capacity in mutate's copy-of-
the-slice to append without allocating a new backing array. Therefore mutate
happy bumps the len of its slice copy and mutates the callers slice!

This bit me once in real production code when reusing a []byte array to avoid
allocations. The bug was obviously my fault, but this behavior can be easy to
inadvertently trigger if you're trying to avoid allocations!

 _Edit: fixed code formatting. It 's 2018 YC, please please please implement
at least a subset of markdown._

~~~
howeyc
Indeed, that's why one of the go versions (I don't remember which) added the
capacity as the third argument when "slicing."

[https://play.golang.org/p/FCm8nOElz2n](https://play.golang.org/p/FCm8nOElz2n)

------
Groxx
Nils are among the biggest wats in Go in my experience. "useful nils" just
compounds "the billion dollar mistake" into something even more nefarious and
even harder to identify during code review.

Along similar lines: WAT 15. Under what circumstances do you expect a nil var
return to become non-nil? _Did you even know that was possible?_

~~~
masklinn
> Along similar lines: WAT 15. Under what circumstances do you expect a nil
> var return to become non-nil? Did you even know that was possible?

Yeah. Typed nils are a horrible, horrible feature: when you cast a value to an
interface, it creates a fat pointer of (Type, Value). From a nil, that's (nil,
nil) but from a nil Foo that's (Foo, nil). And since `==` just does a straight
value comparison, (nil, nil) != (Foo, nil).

This actually has an official FAQ entry telling you to go fuck yourself:
[https://golang.org/doc/faq#nil_error](https://golang.org/doc/faq#nil_error)

~~~
seandougall
I really enjoyed Go until I started trying to work with interfaces. The fact
that you have to understand the implementation details at this level in order
to use them reliably, IMO, shows that when the Go team talks about language
simplicity, what they really mean is _compiler_ simplicity. At some point, the
complexity starts getting offloaded onto the end developer.

That said, the fact that the crowd only offered an incorrect guess four times
out of 16 is telling. This really didn't have the same feel to me as the
original WAT talk, which is really full of truly strange things.

~~~
slrz
It doesn't have anything to do with implementation details, even though the
weird set of rules from the article implies that.

Dump those rules for a much simpler insight:

When comparing interfaces, type _and_ value must match to compare equal.

So, (nil, nil) compares NE to (*myFancyErrorType, nil) for the same reason
that (float64, 0) and (uint16, 0) do.

It isn't all that complicated. I don't see a real and non-insane alternative
to it. Do you?

~~~
Groxx
Sure there is. Do what the vast majority of other languages do - don't bundle
behavior and nil like this. Or even better, make nullability a proper first-
class citizen so this nonsense can be prevented at compile time.

------
leshow
> Go avoids magic

What? Go is all about magic. All the std lib stuff that is able to take any
type works by magic.

Those "obscure rules" (which I prefer to call being able to reason about code)
look like they are going to get added in Go 2.

It's odd how programmers think polymorphism is this obscure thing when it's
available in almost every typed language.

~~~
seandougall
I would add garbage collection to that. Being able to ignore memory management
and trust it to be handled at a non-deterministic time by a separate entity
outside of your control is certainly useful, but it's also about as "magic" as
programming gets at the language level.

------
jonbodner
Hi, I'm the presenter AMA.

One general comment; not every question was intended as a WAT. Some were
setups to introduce a WAT.

~~~
rapidloop
"The default type (used for inference) for an int constant is int, which is a
32-bit type" \-- this is not true, the size of int is platform-defined. See
[https://golang.org/ref/spec#Numeric_types](https://golang.org/ref/spec#Numeric_types).
The error you're seeing is because on 32-bit platforms where int is 32 bits,
2^64-1 does not fit an int, and on 64-bit platforms where int is 64 bits,
2^64-1 does not fit an int either (int's are signed). This will work on 64-bit
platforms though:

    
    
      a := math.MaxInt64
      fmt.Println(a)

~~~
jonbodner
Unfortunately, the transcription doesn't match what I said. The video is now
available at [https://youtu.be/zPd0Cxzsslk](https://youtu.be/zPd0Cxzsslk)

------
mikelward
I feel like WAT3 is the only real WAT.

Looking up a key in a map you didn't "make" gives you the zero value. I think
that's consistent with much of Go. But if appending to a nil slice works,
assigning to a key should never panic, even if you didn't "make" it first.

------
caffeinewriter
I feel like every language should have at least one "wat" style talk or
article. Even for languages you adore, it's important to understand the
quirks, shortcomings, and issues in a context that might seem absurd to an
outside developer.

------
speps
WAT 16 is the most shocking really. Reminds me of Python 2 where True and
False can be overridden as well, it was fixed in Python 3.

~~~
sacado2
Who does `true := false` in actual code, though? However, in real life, I've
already wondered if using `new` as a variable is bad practice or not:

    
    
        old := x
        new := foo(&x)
        if old != new {
           return &Bar{} // oops, return new(Bar) won't compile
        }
    

That should at least be caught by linters.

~~~
jonbodner
I chose true := false for the humor and shock value.

As you point out, accidentally redefining the meaning of len or new or close
is far more likely.

~~~
sacado2
I'm with you there, I have to admit I already did the true := false trick for
the amusement and bewilderment of my colleagues.

------
stcredzero
_nil is weird in Go, and in most languages with an equivalent concept. It 's a
"hole in the type system"_

In Smalltalk, nil just contains the sole instance of the UndefinedObject
class. It's not a hole in the type system. Instead, it becomes a paradox in
the inheritance system, because it's used as a superclass.

Here's a WAT. Smalltalk is actually strongly typed. It's just that everything
has the same type of Object. (The type system has no holes. However, it's the
size of a thimble!)

------
kbd
I didn't finish reading all the WATs, but if these are the best WATs about Go,
it must be a very consistent language.

The first two, for example, aren't WATs at all. The Go book is very clear that
you need to use the result of `append` to get the modified slice. WAT 4, map
traversal is unordered, is not at all a WAT. WAT 7, maps are reference types,
is also not surprising at all. WAT 8 is also not a WAT, defers are defined to
be processed in LIFO order, and the rest falls out of how named returns work.
This was a waste of time :(

~~~
lostmyoldone
I think that what you are saying is true, in a manner. To my eyes, it is
however in the manner that for the small syntax and the small number of
idioms, it _consistently_ surpises me how weirdly they interact.

~~~
kbd
> it consistently surpises me how weirdly they interact.

What exactly surprises you?

I just read the Go book and none of what I saw was surprising in the least. I
consider a WAT something like Python mutable default arguments that are
certain to surprise every Python programmer at some point.

------
nemo1618
You call that a WAT? I'll show you a WAT.

[https://play.golang.org/p/ePRpDbaFaRl](https://play.golang.org/p/ePRpDbaFaRl)

~~~
Buge
It is confusing, but at least the io.Writer documentation calls it out as bad
code

[https://golang.org/pkg/io/#Writer](https://golang.org/pkg/io/#Writer)

>Implementations must not retain p.

------
songgao
This old blog post has a pretty good explanation about how Go slice works:
[https://blog.golang.org/go-slices-usage-and-
internals](https://blog.golang.org/go-slices-usage-and-internals)

Once you realize slices are just (pointer, length, capacity) structures, and
the structure itself is copied by value, the first 2 WAT is pretty trivial.

~~~
erik_seaberg
It's kind of broken that slices are immutable but maps aren't, and that
reading a slice can panic but reading a map can't.

~~~
SamWhited
Reading from a nil slice and map feel consistent to me. If you read from a nil
map you get the same result as reading a key that is not in a non-nil map
(because that key is clearly not in the map because it's nil). Similarly, if
you read from a nil slice you get the same result as reading an index that's
not in the slice, an out of bounds access.

------
ainar-g
WAT 3 and WAT 10 were surprising for me.

WAT 3 becomes less surprising when you consider that a method on a pointer can
be called and can return a valid result even if the pointer is nil.

WAT 10 just seems inconsistent. I said this before, and I'll say it again:
shadowing does more harm than good.

The rest are pretty trivial for anyone who worked with the language for over a
year or read the documentation carefully.

------
Thorrez
Now combine that with the extremely confusing net.IP and net.IP.To16() and you
can get a sneaky bug that I've seen in practice.

[https://play.golang.org/p/SGgR4RSCPvM](https://play.golang.org/p/SGgR4RSCPvM)

------
jonbodner
The video for the talk is now available:
[https://youtu.be/zPd0Cxzsslk](https://youtu.be/zPd0Cxzsslk)

------
bradknowles
So, for those of us who are not GO-fers, what is a WAT?

~~~
int_19h
It's not a Go-specific thing. I think this presentation started it:

[https://www.destroyallsoftware.com/talks/wat](https://www.destroyallsoftware.com/talks/wat)

------
IshKebab
Except for the first two I don't these are any where near as bad as the
original Wat ones.

Still, nice list of small gotchas.

------
kc1116
Feels like a lot of work went into coming up with these WATS

------
cliffordthedog
Number 4 isn't really a wat, it's just a design choice. other languages offer
maps that do retain order.

