Refusing new features

Avoid leaky abstraction as much as possible. If an abstraction is not solid enough, you’d rather not use them at all. Go is designed like this, the features are carefully selected. If a feature is not solid enough, it will not be allowed to enter Go.

No implicit string concatenation

Rob: That used to be in the language but was dropped when the semicolon insertion rules went in. You need the + to be able to span lines, and if you can’t span lines operatorless concatenation is close to pointless.

No Implicit conversion

Rob: In any case the clarity of Go’s strictness is worth the occasional conversion. A huge class of bugs is simply gone, and a huge piece of tricky language in the specification never needed to be written.

Russ: Inserting automatic widening means giving up the “named types are not the same” rule, which makes it pretty much a non-starter.

Octal literal

Octal numeric system is rarely used, why does Go support them?

From Wikipedia “Octal”: Octal representation of non-ASCII bytes may be particularly handy with UTF-8, where any start byte has octal value \3nn and any continuation byte has octal value \2nn.

Null pointer (billion-dollar mistake)

Jonathan: in the eyes of the Go designers, who clearly don’t view it as a billion-dollar mistake, and in my eyes as well, having spent days debugging (or failing to debug) race conditions, but rarely more than an hour per null-pointer bug).

rog: even in languages with no nil, such as Haskell, you can still get exceptions from using things that have an unexpected form. e.g. head [] that’s not too different from a nil pointer exception. so even if you go the non-nil route, you may still end up paying a good proportion of the “billion dollar” price.

Return errors rather than exceptions

Raymond Chen: It’s easy to write bad code, regardless of the error model. It’s hard to write good error-code-based code since you have to check every error code and think about what you should do when an error occurs. It’s really hard to write good exception-based code since you have to check every single line of code (indeed, every sub-expression) and think about what exceptions it might raise and how your code will react to it. (In C++ it’s not quite so bad because C++ exceptions are raised only at specific points during execution. In C#, exceptions can be raised at any time.)

Nil channel always blocks (both send and receive)

Rob: It’s for consistency with select. The semantics of a nil channel are the same regardless of how it is used. It’s useful that it blocks in a select, so that’s what it does outside a select.

Reading a closed channel always succeeds with zero value

  1. the read cannot block because then it will not be distinguishable with nil channel.
  2. the read cannot panic because the writer close the channel to signal that it has been closed, not to panicking the reader.
  3. Then the read has to return, which value to return? Since the channel has been closed, so the only possible value is a fake one – default zero value. an additional result of type bool reporting whether the communication succeeded. e.g. v, ok := <-ch.

Writing a closed channel panics

The channel should only be closed by the writer(not reader), so if a writer close the channel and then write it again, it should be a programming bug and panic is the right behavior.

Full qualified package name


Using . as an import name is part of the language and necessary in some cases to solve difficult naming issues but it’s not the intended style. Package authors can depend on the fact that their code lives in its own name space. If . becomes standard, then all packages in effect become a global name space and the style changes. Instead of


the function must be called something like


to avoid colliding with New from other packages. And then what if Foo is itself a common name such as Buffer? You rapidly end up in the Java space of very long names for everything.

This convention we have, using the package names always, is deliberate. Yes, sometimes it makes for more typing but we believe in the long run it means less typing.

Slice type is a builtin type

To support variadic arguments.

len returns int rather than uint


Unsigned types are not a good choice for counts, because they have odd behaviour at 0, a common case. Signed types have odd behaviour at very large and very small values, an uncommon case.

What is type T []T

A slice is a small object containing a pointer to the underlying array, so T is the slice object containing a pointer to the underlying array, whose element is T itself.

Each if, for, and switch statement is considered to be in its own implicit block

It means a for loop has two nested blocks, the outer implicit block is for the loop variable, and the inner block for the loop body:

for i := 0; i < 5; i++ {
	fmt.Println("outer i:", i)
	i := 999
	fmt.Println("inner i:", i)

The implicit block has to be introduced to for statement so that the the value of the loop variable can be shared between iterations, but I am not sure why it is useful for if and switch.