top | item 31734110

Why I Don't Like Golang (2016)

140 points| bravogamma | 3 years ago |teamten.com

222 comments

order

implying|3 years ago

They've fixed the import / modules situation to a point where it's usable and much improved, and generics have been added.

However, the issue this brings up about structs / types not explicitly declaring which interfaces they implement is a real and unaddressed problem, especially in large codebases. The only tool that I'm aware of that finds implementations is GoLand, at steep JetBrains prices.

Figuring out what type an API is asking for should not require reading every line of code in the package, and slows down every developer of large Go projects

voidfunc|3 years ago

When developers who usually make 6 figures of money complain about $90/yr IDE price I just cannot help laugh. Actually it's even better... the price elevators down from $90 -> year2 90 - 20% -> year3 90 - 40%.

Over three years that's like $150-$200 total and it will save you so many headaches. But that's a steep price? Are you kidding? Why do developers hate tools that cost money when they save them time and allow them to do more?

richardsondrew|3 years ago

> However, the issue this brings up about structs / types not explicitly declaring which interfaces they implement is a real and unaddressed problem

You can do this with a line of code below the struct definition, something like:

  var _ <interface> = &<struct>{}
The compiler will also generate helpful errors if the struct doesn't implement the interface.

Not requiring struct definition is a great feature in golang. I'm able to add an interface to a struct defined in another library to inject a different implementation for unit tests.

mseepgood|3 years ago

> is a real and unaddressed problem

No, it's not. It's one of the best features, and I wish every language did this. At least TypeScript does it, too.

sam0x17|3 years ago

It's more than that though. There is just no standard library to be spoken of. I mean things like the most basic string processing methods, etc., are left to the programmer to sort out themself. Every time I've done contracting work on Go projects, I'm shocked by the things I have to implement from scratch (not that it isn't fun) that we take for granted in every other modern compiled languge I've tried (Rust, Crystal, Nim). I know this is more of an environment thing than a language thing, but in practice, language and environment are so intertwined it's pointless to not consider them together.

skybrian|3 years ago

Not sure what you mean. The types consumed by an API are declared (though they might be interface types), and reading the godoc will give you a good overview of a package.

When would you want to look for all implementations of an interface? Is this something like an abstract syntax tree?

grey-area|3 years ago

The interface defines the behaviour required at the point of use - that’s the point of them. You should not need to know which types implement an interface and if you do things are deeply broken in your codebase.

I’ve developed large Go codebases and never had this problem so your last sentence is false. In addition this is not an issue other go developers I’ve spoken to have ever worried or talked about.

serial_dev|3 years ago

> structs / types not explicitly declaring which interfaces they implement is a real and unaddressed problem

Isn't it a feature? You can have a "writer" as long as it can "write", and then use that writer anywhere where a function expects a writer?

sudo_chmod777|3 years ago

> However, the issue this brings up about structs / types not explicitly declaring which interfaces they implement is a real and unaddressed problem, especially in large codebases. The only tool that I'm aware of that finds implementations is GoLand, at steep JetBrains prices.

So are you trying to find implemented interfaces or interfaces' implementors?

Former: In Vim I can use `:GoImplements`, which internally calls `guru` I guess.

Latter: `gopls` supports this.

I agree it's still a pain that one can not tell directly from code what interfaces a struct implements tho.

Beltalowda|3 years ago

> However, the issue this brings up about structs / types not explicitly declaring which interfaces they implement is a real and unaddressed problem, especially in large codebases. The only tool that I'm aware of that finds implementations is GoLand, at steep JetBrains prices.

Alan Donovan's guru tool could do this, but it kind of broke with modules and it was never updated and deprecated in favour of gopls. I don't know if gopls added this yet (I never really found a use for it).

I don't think it's very hard to write a tool for this though; parsing Go code is fairly easy and the stdlib provides a decent API for it. I think you could have a functional tool in a day if you wanted to, although without any caching it might be a little bit slow on larger code bases.

BreakfastB0b|3 years ago

It's not great, but I use this pattern to check / enforce interface membership.

  type Bar interface {
     BarMethod(int, int) int
  }

  type Foo struct {}

  // Error: Foo does not implement Bar (missing method BarMethod)
  var _fooImplementsBar Bar = Foo{}

zbobet2012|3 years ago

I highly disagree with the interfaces criticism.

Firstly You can literally just use one of the dozens of go lsps or code tools to search for API invocations to find what structs are passed/called into it. More importantly if you need to know you've written bad code. The entire point of an interface is that you SHOULDN'T need to know the underlying type. If you do you've violated the entire point. Just pass concrete ones. I've written Go for years and never had a problem with this, even in large open source projects like Kuberenetes.

Secondly, the criticism about flipping return values order/meaning isn't a criticism of interface being structurally typed (https://en.wikipedia.org/wiki/Structural_type_system). If you return int, int and the second int "should be even", you should have defined a type "Even" and returned int, Even*. Systems which are structurally typed can demonstrate functional extensionality and (https://github.com/FStarLang/FStar/wiki/SMT-Equality-and-Ext...) and check whether you've flipped the arguments, which would be a more valid criticism (but such checks are expensive and conflict with compile time requirements). Also Java has the same problem, if you define two interfaces with the same method signature and a single class implements both you can't disambiguate.

Thirdly, the structural typing has a huge advantage, namely looser coupling and more tightly defined interfaces. If you follow the "accept interfaces return structs" go idiom, you'll see why. An open source library that does so leaves their returned structs open to be used by consumer code, that itself uses interfaces, without modification required. This means most go code has small, tightly defined interfaces, where every function on the interface is invoked in the relevant function.

For example if you have a library with this definition:

type Baz struct {}

func (b Baz) Foo(){} func (b Baz) Bar(){}

I can use Baz in my code like so:

type Fooer interface { Foo() }

func DoSomething(f Fooer) { }

And use the underlying library, while being decoupled from it, without having to modify it.

Fourthly: You can explicitly say a type implements an interface...

* A good study: https://blog.boot.dev/golang/golang-interfaces/

* In Idris we would do:

even : Nat -> Bool even Z = True even (S k) = odd k where odd Z = False odd (S k) = even k

int -> even doubler a = 2 * a

raxxorraxor|3 years ago

I don't write Golang but honestly that was one of the features that interested me the most. Automatic type compatibility. I get the criticism as you will depend on tooling to get some expected convenience. But if you are used to void *ptr anyway...

To be fair, most APIs need documentation and code is just plainly not enough. At least if we are talking about specialist interfaces that aren't just another web framework.

saturn_vk|3 years ago

Doesn't the official language server have support for this?

x3n0ph3n3|3 years ago

I'm still baffled by their decision around date formatting.

https://www.godateformat.com/

Beltalowda|3 years ago

I find 15:04:05 on Monday Jan 2nd 2006 a lot easier to remember than all those strftime %-verbs. I certainly don't see how "%B %e, %Y" is any better than "January _2, 2006". %B for what? Bonth name? And %e for "d for day plus one so %e".

thegeekpirate|3 years ago

I wrote https://golangti.me because I never remember _any_ of the formatting types (besides Excel (which isn't as extensive)—d, dd, ddd, dddd, m, mm, mmm, mmmm, mmmmm...).

philliphaydon|3 years ago

This makes no sense to me. First time I’ve been confused about date formatting.

LVB|3 years ago

>The tried and true approach of providing a compare method works great and has none of these drawbacks.

Agreed! https://pkg.go.dev/sort#Slice is wonderful. (Added a bit after this article was written, I think.)

srer|3 years ago

Lets see what the Go doc example looks like:

  sort.Slice(people, func(i, j int) bool { return people[i].Name < people[j].Name })
In Python one might write:

  people.sort(key=lambda person: person.name)
Or in Rust:

  people.sort_by_key(|person| person.name); // sort_by is also an option...
I think it's worth calling out exactly what is happening in the Go example:

- We create a closure that captures the people slice

- We pass the people slice and the closure to the Slice function

- The Slice function mutates the people slice, and because the closure captured the slice it sees these mutations too

I get why the Go team wrote sort.Slice like that, and it was perhaps the best they could have done with the language features...But I think we're going to have to agree to disagree on how wonderful it is compared to other languages ;).

benhoyt|3 years ago

My thoughts on his points, from someone who really likes Go and has used it heavily on small and large projects (1M LoC):

1. Probably a matter of taste, but I love this feature, just because of the lack of noisy public/private keywords everywhere that you see in Java et al. It also means you can tell from a usage (not just the definition) that something is exported, which is often useful.

As far as renaming goes, either rename the definition and see where the compiler complains, or get your IDE to do it (I use GoLand, but good things are said about gopls).

As for his example, the idiomatic way to write that is either just call it `usr` or `adminUser`, or use `user := &user{}` which is valid (if a little confusing).

2. This is a feature: it allows you to define interfaces only where you need them (on the consumer side), and you define the interface with only the methods you actually need. This means that when you add a bunch of new methods on the implementation, you don't need to change all the consumers. Go interfaces are amazing.

The downside he discusses almost never happens: it's surprising, but even in large projects I've never had structs accidentally implementing interfaces or a IsAdmin method being implemented with reversed polarity by accident.

3. Definitely has its downsides. Tooling helps find unchecked errors. Though I've found the biggest downside to explicit errors is the verbosity. You do get used to it, and the explicitness is at least clear.

4. There are a couple of "magical" things like this, but they're well known and documented, and simple to fix if you run into them. I love the fact I can just name a file foo_test.go and add TestFoo methods, and "go test" finds them automatically.

5. I have not found this to be the case, and in the rare cases it does happen, the compiler tells you loudly and it's easy to fix.

6. Yeah, this is a slight pain, but the semi-official "imports" package (golang.org/x/tools/imports) fixes it up, so you just run generated code through that (and it auto-formats the code as well). It's a couple of lines of code. See: https://github.com/benhoyt/prig/blob/2df1b65a2bdf34c10bb5e57...

7. Yeah, I wouldn't mind a ternary operator. Easily misused, which is why they didn't add it, but it would be really nice used judiciously, rather than the 4-line if-else block.

8. Fixed by sort.Slice, which avoids the need for Len and Swap (and even more so by the new generics "slices" package, coming soon). I guess this was added after the article was written?

9. Fixed by "Go modules", which is really well designed and works well (though opinions differ).

10. Fixed with generics being added in Go 1.18. And generic helpers like "slices" and "maps" packages coming soon.

11. Yeah, slightly annoying for newbies, though as he mentioned, tooling tells you. I do like the control you (can) get over allocation and memory management with Go slices.

As far as his summary goes (eg: the type system getting in your way for large programs), I have definitely not found that to be the case. The author doesn't like Go, and that's okay! I don't like Java. :-)

moron4hire|3 years ago

On point #2:

I don't know anything about Go, but I've spent a lot of time working in a lot of languages. Structural typing is a bad idea. There are a few, limited cases where it's necessary, e.g. we have it in TypeScript because TS ultimately has to work within the limitations of JS. But if you don't have those sorts of limitations, intentionally implementing structural typing in your language is borderline negligent.

Interfaces don't just define a bag of method signatures. Interfaces are semantic, too. An Employee, a Gun, and a ClayPot might all have a method named `fire()`, but they all mean different things. But with structural typing, we can load our Employees into a Kiln and give HR a Gun to downsize the company, and nothing will stop us.

gorgoiler|3 years ago

I’d like to get into go but in the past I’ve always been burned by not being able to quickly refactor my code.

If I want to change the contract of get_kittens so that it returns a set instead of a list, I found it quite tiresome to then go to all the call sites of get_kittens and change their types to match.

What was I doing wrong?* Perhaps there’s a cleverer tool out there that can infer and implement these type changes for me, automatically?

* using vim? [joke]

kstenerud|3 years ago

Although go has many deficiencies, this is not one of them. When you use "var" or "val", it works like "auto" does in C++.

sudo_chmod777|3 years ago

I also use Vim.

1. Use `:=` whenever you can, so types are inferred by the assignments/allocations 2. Alternatively query all references from `gopls` and put the results into quick fix list, then `:cdo`

taki_mekhalfa|3 years ago

This is true for all typed languages though..

thefaust|3 years ago

Why isn't Go good for large projects? Kubernetes is an example of a huge project built in Go

ihateolives|3 years ago

This is stock example. Anything else?

yashap|3 years ago

Agreed with the article, though obviously it’s a bit dated (especially around generics and package management).

My take is that Go is basically the new Java, with fewer abstractions and faster compilation. Although, the pre-Java 8 Java, before Java started to get a bit functional.

Like Java it’s a practical, imperative, statically typed, garbage collected language with very good performance. Also like (pre-Java 8) Java, it’s very verbose, doesn’t allow for much “elegance”, and many find it not very fun to write. But it is a pretty decent language for getting shit done.

Overall, I don’t really enjoying writing Go, but it’s not the worst either. I’d code in it if necessary, but wouldn’t chose it for a personal project. I just have more fun and am more productive writing code in concise, mixed OOP/FP languages like TypeScript or Scala, even if they don’t compile as fast.

richardwhiuk|3 years ago

It's the new Java 4. Eventually they'll add the missing stuff and it'll get current with Java 6.

kaba0|3 years ago

I would really like to see a benchmark on compilation speed. Sure, Go’s compiler is really fast by not doing any fancy optimizations - but.. have you seen javac’s byte code output? It barely does constant propagation, because it can get away with it due to JIT. So if anything, javac just starts up a bit slower, or the build tool does something at first start but otherwise java programs literally compile in a blink of an eye.

digianarchist|3 years ago

Surprised you compare it to pre-Java 8 considering functions have first class support in go.

vippy|3 years ago

The Scala ecosystem is super cool, once you're able to groc all of the neat stuff happening, sort the good tools from the bad, etc. Love http4s, scala-js, scalajs-react, doobie, cats, etc.

ridof|3 years ago

I just leave this here: https://github.com/golang/go/issues/49383

After such public disregard to the communnity and contributors as a whole, the talk about good or bad has no meaning until they learn the basics. For example how to work with community, and the fact that you have to provide your phone number in order to fix urgent bug or implement some feature is a plain stupid(or rather malicious).

Just imagine you've spent your free time working on the fix or feature, and instead of getting appreciation or sometimes bounty or just nothing, you're being "charged" to contribute. Yeah, they really think it's normal that contributors have to give up PI to the advertisement company that were accused of violating privacy many times before. They basically treat contributors, tech-savvy users who provide free labor, like their usual consumers. Just think about it for a second, this is insane.

weatherlite|3 years ago

As a new Go user I seriously don't get the hype. It feels like C with some (not many) niceties thrown on top, that's not what we expect from high level languages. I am still waiting for the tada moment, hope it comes.

Barrin92|3 years ago

I don't think there is a tada moment but for me that's ironically enough what made the language deserve some hype. I like C, I never liked sophisticated languages that impose too strong of a style on the developer, a garbage collected, nice C with good performance and practical choices is pretty much what I wanted for a really long time.

readlikeasloth|3 years ago

I don't want to spill the beans here, and well: you figured it already out by yourself.

throwawaylala1|3 years ago

What are you using it for?

ngalaiko|3 years ago

another person who can’t get over his java stockholm syndrome in three (!) years

one particular thing that tells that is the attitude to interfaces:

while in java (and most languages) interfaces are used to tell which contracts a class implements, in go it’s reversed. you must declare interfaces to _require_ certain contracts, for arguments in your functions

for example:

type interface Operator { Operate(int, int) int }

func IntOparation(a, b int, op Operator) int { return op.Operate(a, b) }

this is a major difference highlighting the ownership boundaries: * when I write a package and rely on a 3rd party contract, instead of referencing it and adhering to it, I will copy-paste parts that I need to my package and be independent

xigoi|3 years ago

> if I name my source file i_love_linux.go, it won’t get compiled on my Mac

What the fuck?

tandr|3 years ago

There some file name conventions in place in Go [1]. Files ending in *_test.go are going to be run dufing `go test ` invocation, but not compiled during production build run. In general, last parts of file name reflect "tags", or a platform that this file should be built for. So, in the case of *_linux.go file will only be compiled when it targeted linux platform. Allows to have a file say file_windows_amd64.go and file_linux.go that have functions with same signature, but only one will be picked up for a target platform. Sort of like #ifdef , but at file-name level.

There is a way to specify what file is actually targeting inside the file, through //go: "pragmas" too

[1] https://stackoverflow.com/questions/25161774/what-are-conven...

pjmlp|3 years ago

Others already explained why.

Now a bit more background, this goes back to C and C++, and is considered a best practice to name translation units as name_os_arch or similar pattern, with the header file being only name, instead of #ifdef spaghetti.

This is actually one of the few things I think Go did right.

bamboozled|3 years ago

Yeah, "If I do <some obscure thing> in <chose any language>, and it doesn't work how I like, I don't think it's good"

quickthrower2|3 years ago

Searching around a few blogs mention _linux.go files are only compiled on Linux. But I see nothing in the official documentation, will at least in my quick searches.

It will catch someone out who uses BSD as an acronym for something in their domain model (Bulk Sales Discount?) then xyz_bsd.go doesn't compile.

shantnutiwari|3 years ago

Why does Go get so much hate here? Almost all front page articles are about how Go suCkS mAn

I know Go isnt great or perfect, but still, why so much hate? I seriously want to know

elpatoisthebest|3 years ago

To be 100% fair, this post is a full 6 years old now.

But also, I don't think it's specifically hate, it's more of a reaction to the overwhelming wave of posts here (and basically on every programming forum) from around 2014 to 2018.

Go was so hyped it was unavoidable that if you were starting a project, dozens of comments would be shouting at you to use go.

Some posts are people finally getting to say, "I told you so, but I was against the crowd a few years ago" Some posts are people saying, "Go doesn't really fit this use case" Some posts are people just academically sharing the language features you don't get when you choose go.

Basically, in my opinion (and as a developer of a large go codebase that I really love), thousands of people hyped up go as a silver bullet or a "near-perfect" language. This is obviously not true, go has many downsides. When you tried to bring them up before, you were downvoted and pushed aside for the hype. Now that people are maintaining legacy go code, there's more appetite for these conversations about go's tradeoffs.

morelisp|3 years ago

For many years Go has been displacing C/Python/PHP/Ruby/backend JS in situations where it's a clear win or at least the tradeoffs in tooling, speed, type systems, distribution etc. can be expressed in terms of agreed-upon technical priorities.

Now there's a lot of Go code in the wild, projects people want to use or extend and programmers who will choose it by default, and it's starting to displace C#, Java, and C++ where the comparisons become a lot more preferential and vague. Do you want a faster GC or deterministic allocation? Nominal or structural interfaces? People fear change, especially in a field where change is only loosely correlated with improvement.

crowdyriver|3 years ago

> Go doesn’t have exceptions. It uses multiple return values to return errors. It’s far too easy to forget to check errors...

Yes, because it is easy and predictable to track exceptions in nested try catches and hidden control flow.

nemothekid|3 years ago

>There’s no ternary (?:) operator. Every C-like language has had this, and I miss it every day that I program in Go. The language is removing functional idioms right when everyone is agreeing that these are useful.

And everyone agreed so hard that it was removed from almost every modern C replacement (Rust, Nim, Zig, Elixir, Kotlin).

dragonwriter|3 years ago

Rust has if/else (ternary) and match (N-ary) expressions, so it doesn't need a separate ternary operator. All of the other “C replacements” listed (which are a weird list for that description, especially Elixir, but whatever) have at least if/else-expressions, which, again, are ternaries.

mikebenfield|3 years ago

> And everyone agreed so hard that it was removed from almost every modern C replacement

They removed it because they made the regular "if" statement into an expression, so the separate "ternary" operator expression is superfluous. Rust et al made the "if" situation better than C; Go made it worse.

Hasu|3 years ago

This is entirely off topic, but how are Elixir or Kotlin "modern C replacements"? Elixir is built off of Erlang and has influences from Ruby and Clojure, and Kotlin is a replacement for Java.

Rust, Nim, Zig, and Go are all fair game in that list, as far as I know being a C replacement is in the mission statement for the creation of all those languages.

edflsafoiewq|3 years ago

All those languages have if-expressions, which is all ?: is.

bobbylarrybobby|3 years ago

Ternary syntax was statement-oriented languages' compromise to get one bit of nontrivial expression syntax into the language. In Rust everything is already an expression, including if-else, so additional syntax isn't necessary.

tobyhinloopen|3 years ago

Elixir has the if-else being an expression.

foo = if x, do: 1, else: 2