Okay, I admit the title is a little presumptuous. I’ll tell you one more thing: I love a headline that’s outrageous. It grabs attention. Anyway, in this blog POST I’m going to try to prove that Go is a poorly designed language (revealing story: it is). I’ve been tinkering with Go for a few months now, and I think I ran my first HelloWorld program sometime in June. I’m not very good at math, but it’s been four months since then, and I already have several packages on Github. Needless to say, I still have absolutely no experience using Go in production, so take all I say about “coding support,” “deployment,” and all that with a grain of salt.

I like the Go language. I’ve loved it ever since I tried it. It took me a few days to get used to the language habits of Go, to overcome the difficulties of not having generics, to understand the weird error handling and all the typical problems of Go. I read Effective Go and a lot of The articles on Dave Cheney’s blog and kept an eye on everything that was going on and so on. I can say I’m an active community member! I love Go and I can’t get out of it — Go is amazing. Yet in my humble opinion, contrary to its hype, Go is a poorly designed, poor language.

Go is considered a concise programming language. According to Rob Pike, they went to great lengths to keep the language’s specifications simple and clear. This aspect of the language is amazing: you can learn the basics in a few hours and get right down to writing code that works, and most of the time Go works as expected. You’ll be incensed, but hopefully it works. The reality is different. The Go language isn’t simple, it’s mean. Here are some arguments to prove it.

Reason 1. Slice is not doing it right!

Slicing is great, I really like the concept and some of the uses. But let’s imagine for a second that we really want to write some code with slices. Clearly, slicing is in the soul of the language and what makes Go powerful. But, again, in the midst of the “theory” discussion, let’s imagine that we sometimes write actual code. The code listed below shows how you can do lists in Go.

// Please give me some figures! numbers := []int{1, 2, 3, 4, 5} the log (Numbers) / / 1. \ [1, 2, 3, 4, 5] log (Numbers [2:]) / / 2 \. [3, 4, 5] log (Numbers [1-3]) / / 3. \ [2, 3] / / it is interesting to note that You cannot use a negative index // // numbers[:-1] from Python does not work correctly, instead, // you must do this: // log(numbers[:len(numbers)-1]) // 4\. [1 2 3 4] Well done! // // Now let's insert a 6 at the end: // numbers = append(numbers, 6) log(numbers) // 5\. [1 2 3 4 5 6] // Remove 3 from numbers: // numbers = append(numbers[:2], numbers[3:]...) Log (numbers) // 6\. [1 2 4 5 6] Don't worry, here's a list of the Go language's * universal * best practices // // I especially like... Ha, ha, ha. // numbers = append(numbers[:2], append([]int{3}, numbers[2:]...) ...). Log (numbers) // 7\. [1 2 3 4 5 6] // To copy a slice, you need to do this:  // copiedNumbers := make([]int, len(numbers)) copy(copiedNumbers, Numbers) log(copiedNumbers) // 8\. [1 2 3 4 5 6] // Some other operations...Copy the code

Believe it or not, this is a true picture of how Go programmers convert slices every day. And we don’t have any generic mechanisms, so, dude, you can’t create a nice insert() function to mask the pain. I posted this on Playgroud, so you shouldn’t believe me: Double click and see for yourself.

Reason 2. Nil interfaces are not always Nil 🙂

They told us that “errors in Go are not just strings” and that you should not treat them as strings. For example, spf13 from Docker says so in his brilliant “7 Mistakes in Go and how to Avoid them.”

They also said THAT I should always return the error interface type (for consistency, readability, and so on). This is what I do in the code listed below. You might be surprised that the program actually says hello to Mr. Pike, but is that expected?

package main import "fmt" type MagicError struct{} func (MagicError) Error() string { return "[Magic]" } func Generate()  *MagicError { return nil } func Test() error { return Generate() } func main() { if Test() ! = nil { fmt.Println("Hello, Mr. Pike!" )}}Copy the code

Yes, I know why this is happening because I’ve been reading a bunch of complicated stuff about interfaces and how interfaces work in Go. But for a novice… Come on, man, this is a slap in the face! In fact, this is a common trap. As you can see, Go without these annoying features is a straightforward language to learn, and it occasionally says that the nil interface is not nil 😉

Reason 3. Ridiculous variable overlay

Just in case you’re not familiar with the term, let me quote Wikipedia: “Variable overwriting occurs when a variable declared in a scope (determination block, method, or inner class) has the same name as a variable outside the scope. “It seems reasonable, and a fairly common practice is that most languages support variable overwriting and that’s fine. Go is not an exception, but it is different. Here’s how it works:

package main import "fmt" func Secret() (int, error) { return 42, nil } func main() { number := 0 fmt.Println("before", number) // 0 { // meet the shadowing number, err := Secret() if err ! = nil { panic(err) } fmt.Println("inside", number) // 42 } fmt.Println("after", number) // 0 }Copy the code

Yes, I also realize that the = operator creates a new variable and assigns an Rvalue, so this is a perfectly legal behavior according to the language specification. But here’s the interesting thing: try removing the inner scope — it will work as expected (” after 42 “). Otherwise, say hello to variable overrides.

Needless to say, this isn’t some funny example I thought of at lunch, it’s something real that people come across sooner or later. I refactored some Go code earlier this week and ran into the whole problem twice. Compilation is fine, code checks are fine, everything is fine — the code just isn’t working.

You cannot pass []struct as a []interface

Interfaces are great, and Pike&Co. has always said that it’s what Go is all about: interfaces are about how you handle generics, how you mock, and how you implement polymorphism. Let me tell you, I loved interface when I read “Effective Go,” and I’ve always loved it. In addition to the “nil interface is not nil” issue I raised above, there’s another annoying thing that makes me think interfaces don’t get first-class support in Go. Basically, you cannot pass a slice of a structure to a function that receives a slice of the interface type:

package main import ( "fmt" "strconv" ) type FancyInt int func (x FancyInt) String() string { return Strconv.itoa (int(x))} type FancyRune rune func (x FancyRune) String() String {return String(x)} Type Stringy interface {String() String} // String, made of string representations of items given. func Join(items []Stringy) (joined string) { for _, item := range items { joined += item.String() } return } func main() { numbers := []FancyInt{1, 2, 3, 4, 5} runes := []FancyRune{'a', 'b', 'c'} // You can't do this! // // fmt.Println(Join(numbers)) // fmt.Println(Join(runes)) // // prog.go:40: cannot use numbers (type []FancyInt) as type []Stringy in argument to Join // prog.go:41: Cannot use runes (type []FancyRune) as type []Stringy in argument to Join // // Instead, you should:  // properNumbers := make([]Stringy, len(numbers)) for i, number := range numbers { properNumbers[i] = number } properRunes := make([]Stringy, len(runes)) for i, r := range runes { properRunes[i] = r } fmt.Println(Join(properNumbers)) fmt.Println(Join(properRunes)) }Copy the code

Not surprisingly, this is a known problem that is not considered a problem at all. It’s just another funny thing about Go, right? I really recommend that you read the relevant wiki to see why “passing structure slices as excuse slices” doesn’t work. But think about it! We can do that, there’s no magic here, it’s just a compiler problem. Look, in lines 49-57 I made an explicit conversion from []struct to []interface. Why doesn’t the Go compiler do this for me? Yes display is better than implicit, but WTF?

I just can’t stand it when people look at this bullshit and keep saying “fine, fine”. And it isn’t. This makes Go a terrible language.

Reason 5. Obscure range “by value” loops

This is the first language problem I have ever encountered. Well, there is a “for-range” loop in Go that is used to traverse slices and listen for channels. It’s used everywhere and it’s good. However, there’s a small problem that most novices get stuck with: the range loop is just value-by-value, it’s just value-copying, you can’t really do anything about it, it’s not foreach in C++.

package main

import "fmt"

func main() {
	numbers := []int{0, 1, 2, 3, 4}

	for _, number := range numbers {
		number++
	}

	fmt.Println(numbers) // [0 1 2 3 4]

	for i, _ := range numbers {
		numbers[i]++
	}

	fmt.Println(numbers) // [1 2 3 4 5]
}
Copy the code

Notice that I’m not complaining that there’s no quoted range in Go, I’m complaining that the range is inconspicuous. The verb “range” is a bit like saying “to traverse items”, not “to traverse copies of items”. Take a look at “For” in “Effective Go.” It doesn’t sound like “traversing copy values in slices.” Not at all. I agree that this was a small problem and I got over it quickly (in a few minutes), but an inexperienced Gopher might have spent some time debugging the code and wondered why the values didn’t change. You can at least explain that in “Effective Go.”

Reason 6. Questionable compiler rigor

As I’ve told you before, Go is considered a simple and readable language with a rigorous compiler. For example, you cannot compile a program with unused imports. Why is that? Just because Mr Pike thinks it is right. Believe it or not, unused imports are not the end of the world, and I can perfectly live with them. I totally agree that it’s not right and the compiler is willing to print out a warning about it, but why would you stop compiling for such a trivial matter? For unused imports, really?

Go1.5 introduces an interesting language change: you can now list map literals without having to display the name of the type listed to be included. It took them five years (or more) to realize that display type listings were being abused.

Another thing I really enjoy in Go: commas. See, in Go you can freely define multi-line import, const, or var blocks:

import (
    "fmt"
    "math"
    "github.com/some_guy/fancy"
)
const (
    One int = iota
    Two
    Three
)
var (
    VarName int = 35
)
Copy the code

Well, that’s good. But when it comes to “readability,” Rob Pike thinks adding commas would be great. At one point, after adding the comma, he decided that you should leave the ending comma as well! So you don’t write:

numbers := []Object{
    Object{"bla bla", 42}
    Object("hahauha", 69}
}
Copy the code

You must write:

numbers := []Object{
    Object{"bla bla", 42},
    Object("hahauha", 69},
}
Copy the code

I still wonder why we can ignore commas in the import/var/consts block, but not in lists and mappings. Anyway, Rob Pike knows better than I do! Long live readability!

7. Go generate

First of all, you should know that I have nothing against code generation. For a language as crude as Go, this may be the only way to avoid copying and pasting something common. However, Go:generate, a code generation tool that Go users use everywhere, is now just garbage. Well, to be fair, the tool itself is fine. I like it. And the whole way is wrong. Let’s see, you need to generate some code by using special magic commands. Yeah, code generation is done by some magic sequence of bytes in the code comments.

Comments are meant to explain code, not generate it. But magic notes are a phenomenon in Go today. And the funny thing is, no one cares, people think it’s just fine. In my humble opinion, this is definitely worse than a scary unused import.

Afterword.

As you can see, I’m not complaining about generics, error handling, syntactic sugar, and other typical Go related issues. I agree that generics are not critical, but if you get rid of generics, please give us some normal code generation tools instead of random random bullshit magic comments. If you remove the exception, please give us the ability to safely compare the interface to nil. If you remove the syntactic sugar, please give us some code that works as expected, not some “oops” stuff like variable masking.

All in all, I’ll stick with Go. Here’s why: Because I love it. I hate it because it’s garbage, but I love the community, I love the tools, I love the smart design decisions (hello interface) and the ecology.

Hey dude, wanna try Go?