top | item 12208013

I Love Go; I Hate Go

279 points| tominous | 9 years ago |dtrace.org | reply

316 comments

order
[+] _skel|9 years ago|reply
The most frustrating thing for me, by far, is that Go won't let you import unused packages. When you are commenting stuff out to debug a program, or adding debug statements and removing them later, it constantly requires you to go back to the top of the file and comment out the unused libraries, only to uncomment them later when you've solved your problem.

The fact that there is not a compiler flag to disable this behavior, and that the core team is opposed to adding such a flag (https://golang.org/doc/faq#unused_variables_and_imports), is simply ridiculous and makes programming in Go a real pain in the ass.

I can't think of any other language that does this.

[+] tptacek|9 years ago|reply
I used to feel this way. It was my top complaint about the language. But it, and, more importantly, the use requirement for variables, has saved me from bugs repeatedly; I more and more notice the bugs it's protecting me from as I keep coding in the language. I am rapidly coming to the conclusion that this was very, very much the right call.

I get the sense that most Go programmers use "goimports" to work around the annoyance. I have my Emacs configured to use it automatically. I never even think about imports anymore.

[+] treigerm|9 years ago|reply
I use an Atom extension called "gofmt" which automatically calls goimports[1] every time I save. So any unused imports will be removed and if I start to use new imports that are in my GOPATH or the standard library then it automatically adds them.

[1] https://godoc.org/golang.org/x/tools/cmd/goimports

[+] scott_s|9 years ago|reply
Not allowing the import of unused packages was an explicit design goal intended to reduce compile times. Cribbing from a comment I made years ago:

Take a look at this adaptation of a keynote talk on the design of Go, with the title "Language Design in the Service of Software Engineering": http://talks.golang.org/2012/splash.article Section 7, "Dependencies in Go" addresses this issue: http://talks.golang.org/2012/splash.article#TOC_7. The relevant paragraph states,

The first step to making Go scale, dependency-wise, is that the language defines that unused dependencies are a compile-time error (not a warning, an error). If the source file imports a package it does not use, the program will not compile. This guarantees by construction that the dependency tree for any Go program is precise, that it has no extraneous edges. That, in turn, guarantees that no extra code will be compiled when building the program, which minimizes compilation time.

Further, the talk justifies this approach through their analysis of how their C++ codebase was compiled:

The construction of a single C++ binary at Google can open and read hundreds of individual header files tens of thousands of times. In 2007, build engineers at Google instrumented the compilation of a major Google binary. The file contained about two thousand files that, if simply concatenated together, totaled 4.2 megabytes. By the time the #includes had been expanded, over 8 gigabytes were being delivered to the input of the compiler, a blow-up of 2000 bytes for every C++ source byte.

As another data point, in 2003 Google's build system was moved from a single Makefile to a per-directory design with better-managed, more explicit dependencies. A typical binary shrank about 40% in file size, just from having more accurate dependencies recorded. Even so, the properties of C++ (or C for that matter) make it impractical to verify those dependencies automatically, and today we still do not have an accurate understanding of the dependency requirements of large Google C++ binaries.

[+] andrewfromx|9 years ago|reply
i was shocked by that at first too. Then I came to love it. It's sooooo go. Don't import stuff you aren't using. Don't declare a var and just leave it there un-used. When I go back to ruby and commit that crime, I see why golang did what it did. There's something magical about a golang program that will compile without error. It's worthy of checking in to git. And months later, there will be no unsed imports FOR SURE.
[+] gkya|9 years ago|reply
I really would like to see what happens when somebody forks go to create a sort of Go++ (tm) which, while still compiling code written for actual Go, didn't strictly prohibit unused variables and imports but only issued warnings and, added function templating as a means of generics. Regardless of whether these are good ideas or not to have in Golang, it'd be interesting to follow how the community orientates itself in time.
[+] Sean1708|9 years ago|reply
I think that's the thing that really gets me about Go. I really like that it's opinionated and forces you to write good code, but I just can't understand why they refuse to allow you to turn them off when you need it.

Also doesn't that workaround completely nullify all the arguments they made in that section? And it puts the programmer in a worse position than if they could just turn it off temporarily.

[+] dmoy|9 years ago|reply
I think it depends on what you're using it for. On my day-to-day I'm working with absolutely massive pieces of software that have dependency trees that would literally make me cry if I stopped and thought about it too hard.

In that world, this sort of thing enforced strongly makes me much happier.

For random one-off hacking, yea, it'd be a pain.

Differing use cases lend cause to different tooling.

[+] kmiroslav|9 years ago|reply
It's a simple reflection of the fact that the Go creators have never used a modern IDE.

Anyone using a modern IDE pretty much never even thinks about imports, which are (and should be) automatically managed by the tool, not by the programmer.

[+] eloff|9 years ago|reply
Bikeshedding much? I can't see this being a real problem. There are a lot of other decision that the Go team made, for better or for worse that have much greater effects on real code. Unused imports is a joke.

var _ = unusedImport

Or use goimports. Then you don't even have to think about it.

[+] m_sahaf|9 years ago|reply
Everyone else is suggesting goimports, but here's another trick:

    import _ "unused/package"

This is usually used to benefit from the packages' side-effects (e.g. trigger init() - mostly used in SQL drivers to register the driver).
[+] _ak|9 years ago|reply
> The most frustrating thing for me, by far, is that Go won't let you import unused packages.

If you want to explicitly import unused packages, you just prefix it with `_`, like this: https://play.golang.org/p/ltfQgco22t

Remove the `_` in the import of `crypto/rand`, and the program won't compile anymore.

[+] wtbob|9 years ago|reply
Meh, I just have emacs configured to run goimports on save, right along with gofmt. I never even worry about, unless I have to resolve which of two packages with the same final name component I'm using.

That can be a bit annoying, but it's not terrible. Back when Go had insanely great compile times, it was worth it. Now that compile times are … okay … it's — okay.

[+] tekknik|9 years ago|reply
This also infuriates me (more-so the "know-it-all" attitudes of the devs). What's even more infuriating is how simple of a change this is. It took me about 15 mins to change the compiler, adding compiler warnings and producing a warning instead of an error on unused imports and vars.
[+] PopsiclePete|9 years ago|reply
There's at least one tool to help with that, maybe even more. I have never, not once, manually removed an unused import in the last year of Go programming.

Basically, as soon as I save my file, it gets formatted, linted, unused imports are removed, the project is then compiled and unit tests are ran. I couldn't be happier.

[+] dpc_pw|9 years ago|reply
Oh. Go love-hate relationship post. My turn. So I really like channels and coroutines built into language and used everywhere: it makes some patterns compose nicely and language very productive.

But just as in the article - some of Golang choices are opinionated and IMO just stupid. Lack of assertions is one: Sure programmers are prone to ignoring errors, but Go is not helping at all. Just bans assertions and provides no improvements. No `try!`&`Result` like Rust, not even warning by default if you ignored returned error (IIRC, go lint or go vet checks it) . So what's the point?

How do you decrement an atomic in Go?

AddUint32(&x, ^uint32(c-1))

That's how. Easy to read, and self-explaining. https://golang.org/pkg/sync/atomic/ . I mean... come on.

Unused errors being an error is super annoying, and changing variable capitalization everywhere, because you want to change symbol visibility is another daily frustration source.

Oh, and all my contacts with the community were very unpleasant (maybe it's just me, or bad luck). Any questions ended with some form of "you must do it one true way, otherwise you're stupid". Eg. once I was looking for a way to terminate unit test in case of error, instead of writing `if err != nil { t.Errorf(...) }` over and over again after every line, as in test, every error is just terminating condition. In Rust I would just `.unwrap()` or `.expect()`, which would fail the test, report backtrace, etc if anything unexpected went wrong. All the help that I got was just snarky comments for being lazy.

I will continue to use Go in some projects, as it gets the job done, performance is OK, has some userbase, and ecosystem is quite vital, but overall, it seems to me that it's rooted in same form of stubborn crudeness that has no good reason in XXI century, ignoring years of research and good ideas. For anything more demanding, or fun I would always go with Rust.

[+] EugeneOZ|9 years ago|reply
> Contrast this with Rust whose errors read like mind-bindingly inscrutable tax forms.

It was "lol what?" for me. Rust's compiler error messages are very informative and sometimes contains tips what to change in your code to make it work (not just general words, but code you can use right now, with your variables/functions etc.).

Borrow checker explains step-by-step where ownership starts, where it ends and where you are trying to use it.

Panicking messages - another story, but it's not compiler's error messages, it's runtime. In example of code Adam using as proof, author is using 'panic' and 'unwrap' - reasons to don't expect gentle behavior.

If you can't write code in idiomatic way, try to catch your panics and give more descriptive runtime error message in your code - other users of language shouldn't pay for it.

[+] _ph_|9 years ago|reply
I like Go quite a lot. Mainly I am a Lisp programmer, so I am not looking for the next most high level programming language. I am looking for a language which gets things done, where Lisp doesn't quite fit. For jobs, which traditionally would have been done in C. Programs that run fast and have a reasonable complexity. It brings back the virtues of the Wirth computer language family back into modern times, as with strict type checking and the module system. On top of that it adds memory safetey due to the presence of GC.

Go it easy to learn, as its a moderately sized language, but it has just enough abstractions, that your programs are not held back by the lack of those, without opening the traps too "powerful" languages bring with themselves, namely complexety.

On top of all, the whole infrastructure is very well thought out and a pleasure to use. A very fast compiler which produces static executables, a good module system without header files, a rich standard library, many things. There are quite a few quirks, especially for the beginner, but the more I gain insight, the more I agree with the choices made - with some I don't, but the agree to disagree ratio is surprisingly high. And most of them don't get into the way of writing good programs, which is what counts.

[+] pjmlp|9 years ago|reply
> It brings back the virtues of the Wirth computer language family back into modern times, as with strict type checking and the module system. On top of that it adds memory safetey due to the presence of GC.

Wirth's work also includes languages with GC. :)

[+] devishard|9 years ago|reply
This post is yet another reinforcement of my assertion that the people who like Go only like it because they have no idea what has happened in programming languages for the last few decades. Case in point:

> It brings back the virtues of the Wirth computer language family back into modern times, as with strict type checking and the module system.

Strict type checking? Have you used any other languages besides C? Go's type system is a joke.

Like literally, it's a joke. If you get a bunch of people who know about types languages in a room discussing types, "What about Go?" is guaranteed to get a laugh.

[+] junke|9 years ago|reply
I am really curious about the kind of tasks that are easily done in Go but for which Lisp doesn't quite fit.
[+] comex|9 years ago|reply
Heh: my initial thought was that Go is nothing like Lisp - Lisp has the most powerful metaprogramming around thanks to its macro system, while Go doesn't even have C-level macros, or any of the type-system features some languages use as an alternative approach to metaprogramming. As a result Lisp is notoriously unopinionated, while Go is very opinionated. Lisp is functional, Go is aggressively imperative. And so on. But I guess the languages share some things: no complex type system, fast compiles, emphasis on simplicity in general, no need for an IDE, old fashioned feel (no, really, that's not a diss necessarily)... and at least Go has reflection.
[+] cespare|9 years ago|reply
GOEXPERIMENT=framepointer will be the default in 1.7, due out in a few days (see https://golang.org/issue/15840), so using tools like perf (and presumably dtrace) will be somewhat improved out of the box.

Edit: and the author mentions that at the end of the post. That's what I get for commenting before I've read the whole thing.

[+] dcw303|9 years ago|reply
I'm a little confused by the author's belief Go needs to be more pragmatic. All the points made in the "I Love Go" section (gofmt, the toolchain, interfaces) display strong qualities of pragmatism to me. The article literally describe interfaces as 'no-nonsense and pragmatic'. And, even if they do not personally like the opinionated nature, it is a prime example of solving questions in a practical way.

No language can be everything to everyone. One thing that encourages me to keep writing Go is the team has done a very good job at communicating their intentions for the language.

It's very clear to me when to write Go and when not to. That's a sign of strength to me, as it shows the language has the confidence to tell you "perhaps I'm not the best choice for this particular problem".

[+] tuxt|9 years ago|reply
>It's very clear to me when to write Go and when not to.

Would you care to elaborate on this? :)

[+] fauigerzigerk|9 years ago|reply
My favorite little annoyance with Go is the way the := operator behaves with multiple assignment. It declares a new variable if there is no variable of that name in the current scope. But it refuses to do that if there are struct fields involved.

  var t int
  t, err := someFunc() // that's fine
but this doesn't compile:

  var s struct{ x int }
  s.x, err := someFunc() // doesn't compile
[+] garfij|9 years ago|reply
I've run into that as well, but I've since come to favor avoiding that pattern entirely as a code smell.

In your example, you end up modifying the struct value x, even if there was an error. Maybe there are cases where that is the correct behavior, but even then I would advocate for explicitly setting the struct value after handling the error, to make it very clear that it was your intention to do so, rather than doing it be accident.

[+] crench|9 years ago|reply
Weak opinions are what turn languages into unreadable mishmashes of overlapping mechanism.

Go's opinions were, for me, especially helpful while learning the language.

[+] fuzzy2|9 years ago|reply
I don't know, I found debugging with IntelliJ's Go plugin intuitive and well-working. Pretty sure it uses Delve under the hood.

I think strong opinions are helping Go not being "yet another language". So that's good. Like most things, it isn't for everyone.

[+] jokoon|9 years ago|reply
I have great hopes for D since Andrei Alexandrescu joined to work on it.

When you see all those new system languages, (go, rust, swift), D was there before them, and did not pretend to be a new cool thing with good ideas, it just tries to be a nicer C++ to work with.

I don't need to do things the right way, I just need a language that is usable and doesn't try to do the job for me.

[+] donatj|9 years ago|reply
I think a persons opinion on Go really depends on where you're coming from and what tooling they are used to. As a PHP/Node/Ruby dev I find Go does everything PHP/Node/Ruby does, faster, and more reliably due to static typing. Often more verbosely though.

I've simply never wanted for the things he asks as I've never had them. It's all in the expectations is my point.

[+] zzzcpan|9 years ago|reply
Those are really small annoyances compared to everything else in Go. I don't mind Go being opinionated, everything is like that anyway and it's kind of nice in some ways. But don't mistake opinionatedness with misdesigned poor quality libraries and experimental ideas.

Take net package for example, it's not opinionated, it's just completely broken and misdesigned. There is so much mess with even the most basic things, like timeouts and cancellations [1][2] that it's almost impossible not to fuck up somewhere.

This is because the whole net library is synchronous and pushed onto experimental ideas of goroutines and channels. Instead of having an actual event loop underneath with timers, signals and everything and use asynchronous writers and readers they just poll fds and wake goroutines when fds are ready, forcing themselves to deal with typical multithreaded concurrency hell.

[1] https://blog.cloudflare.com/the-complete-guide-to-golang-net... [2] https://github.com/golang/go/issues/16100

[+] simplehuman|9 years ago|reply
Honest question: How does a post with just 4 points and posted 11 minutes ago get to be on the front page? Even amazing it is number 2 right now.
[+] dang|9 years ago|reply
As Tomte and jdoliner pointed out, 4 points in 11 minutes would get you on the front page at any time, let alone a slow time like this one. As for why the post would get rapid votes, that's easy: moth-to-flame subject matter.

It's not great to post comments like this taking a thread so badly off topic, let alone jumping to conclusions that the admins are manipulating what is obviously (once you're familiar with it) baseline HN behavior. Love-hate-Go posts are a drug here; and so are all things Rust, which is why those stories are currently #1 and #2. If we did anything to posts like that it would be to downweight them, but I'm tired, and a good pro-and-con language vent seems like a fairly innocuous thing for HN to do overnight (with apologies for our Pacific-timezone-centricity), so have at it.

[+] paradite|9 years ago|reply
The rate of growth of the upvotes w.r.t time is extremely high for this submission. 0.5(just now) to 1(now) upvote per minute.
[+] Tomte|9 years ago|reply
That's normal.

I'm a nobody (to answer the accusation below) and my submissions of articles that are years old and not recognizably "hot" make it to the front page with only three to five upvotes all the time.

Quick upvotes or comments count for a lot.

[+] anilgulecha|9 years ago|reply
Probably because the author is a renowned engineer (dtrace, etc)
[+] reactor|9 years ago|reply
Besides other points mentioned, I think HN crowd has a love/hate relationship with Go (the other one I noticed is Python), so it's natural to go up quite fast.