Hacker News new | ask | show | jobs
by throwaway894345 1516 days ago
I mean, all programming language communities are partial to their language, but among Go there seems to be an unusual tolerance for disagreement and discussion of language issues compared to most other programming languages. But yeah, when you come in guns blazing talking about how certain language features are "shit" and there's no possibility of elegance, people are rightly going to think you're not there for any sort of productive conversation.

For example, I regularly have productive conversations with people in the community about error handling and sum types and generics, including my criticism for the way Go does some of those features. A little civility goes a long way, and this isn't particular to the Go community or even programming language communities in general. Note that there definitely are PL communities that generally can't handle any criticism irrespective of civility, but the Go community isn't among them. Indeed, in my experience, Go's critics are very often much more zealous than its proponents.

2 comments

> Note that there definitely are PL communities that generally can't handle any criticism irrespective of civility, but the Go community isn't among them. Indeed, in my experience, Go's critics are very often much more zealous than its proponents.

This is because Go is a programming language for people who don't care about programming languages. I mean this in the most positive way possible. If you're using Go, it's because you care about the end result of what you're building, usually a backend service or command-line tooling, far more than the code that was used to create it.

Go is not a language where you come up with clever syntax to solve your problem. Go is not a language that makes you feel smart when you write it. Go is not a fun language to program in. Go is a language that gets out of your way, encourages you to solve your problem in the most boring way possible (usually with a lot of for loops) with a predefined level of safety (i.e. static typing, explicit error handling with the `error` type, etc). It's a language for building bridges, not creating masterpieces.

People who are passionate about programming languages would never like Go in the first place, so you don't get too many zealots to sing its praises.

> Go is a language that gets out of your way, encourages you to solve your problem

Maybe I'm just too dumb for Go, but this is not consistent with my experience at all. Go's insistence on pretending that complexity doesn't exist would get in my way all the time. Go's extreme hostility toward FFI calls got in my way several times.

Yea.. i used Go for 5 years. I just can't agree that the simplicity is true. Yes, the language itself is, but it offloads complexity into my program and thus my day to day is jumping around huge piles of logic which could be made for easier to reason about and understand with some actual help in managing the complexity.

Your complex programs aren't easier in Go, in my experience. The simplicity of the language doesn't help me much when my day is spent fighting to figure out how to make a problem which is inherently complex easy to maintain, reason about, and be without bugs.

I want a language that makes my day simpler. Where at the end of the day, my "net complexity" is less. Go leaves all the complexity to you and offers you very few tools to solve this. Bugs, spread out logic, and even runtime costs of the overuse of Interface{} (prior to Generics at least) left me with a lot of things to solve myself. My days in aggregate were more complex with Go.

Just my experiences.

> runtime costs of the overuse of Interface{} (prior to Generics at least)

Generics aren't going to improve this situation - at least not the current iteration of generics :(

It depends. Things like sorting a slice will be faster, for example. But yeah, the current iteration is a bummer.
> Go's inane hostility toward FFI calls got in my way several times.

All languages w/ obligate GC are "hostile" to FFI in some way or another. The Go default implementation also uses split stacks or something for its goroutines, that cannot feasibly interop with FFI code. But it's usually easy enough to just isolate Go code to it's own process/address space and use IPC or network communication to enable the interop one would usually achieve via FFI.

Inversely, virtually all languages with "easy FFI" end up being even more hostile in that a significant chunk of the ecosystem depends on C build tooling which is almost always fragile: C build systems have implicit dependency management, so you don't know what dependencies you need to have installed on your system or where they need to be installed. This means that something which builds on one machine may fail to build on another machine (in the case of build-time dependencies) or that it may run on one machine but not another (in the case of run-time dependencies). It's also opaque to the host build system, so cross compilation becomes dramatically more difficult. Lastly, C is inherently unsafe and insecure in ways that most host languages are not.

In practice, whether by accident or design, the Go ecosystem is really, really nice because it avoids FFI to a high degree. An overwhelming majority of programs can be cross compiled into a truly static binary (it may not even depend on libc unless--as is the case with Windows and MacOS--the host platform requires it). It also means that there are very few "C-shaped libraries", by which I mean thin bindings around some C library which exposes idiomatic C semantics rather than idiomatic Go semantics. Moreover, your programs aren't running a bunch of inherently unsafe code under the hood, and are consequently more likely to be secure as a result.

It's kind of nice that C FFI is possible such that libraries which are unlikely to be ported to Go (e.g., ffmpeg) or which cannot be ported to Go (e.g., opengl) are still available, but not so easy that people pull in C libraries for every little convenience.

The Rust ecosystem does one better and packages the C libraries and build configuration (including making it portable across platforms) as part of the crate. So you just add the dependency to your Cargo.toml and the C library will build as part of the regular `cargo build` process.
Unless something has changed in the relatively recent past, I think you're overselling a fair bit. Not only does the package author have to understand the C dependency well enough to package it correctly on all platforms (basically by verifying the build in a hermetically sealed environment, and who is doing that?), but also the process for cross compiling is (or at least was) pretty complicated: https://www.modio.se/cross-compiling-rust-binaries-to-armv7..... And even then, I'm not sure this will yield a truly static binary (i.e., no dependency on libc).

In Go, it's just `CGO_ENABLED=0 GOARCH=armv7 GOOS=linux go build` for pure Go programs.

While this is true in many cases, it’s worth pointing out that that is the choice of the package author, and is not always super simple to implement. So yeah, much of the time it is nice, but you’ll sometimes also run into these classic sorts of issues, either because the authors do not put in that work or because there’s a bug in the implementation.
And as a bonus, be statically linked with all the benefits that brings.
Serializing a request structure, making an IPC/network call, deserializing the request structure, serializing the response structure, sending it back, and deserializing it ... isn't really a solution when the purpose of an FFI call is typically to fix some performance issue.

Lots of garbage-collected languages make FFI not only easy but plenty fast. Go does neither.

I started out thinking that fast and easy FFI was ideal and being disappointed that Go's FFI was neither. I've since changed my opinion as it's really nice that one can usually get away without pulling any C dependencies into their dependency tree. I wrote more in the sibling comment: https://news.ycombinator.com/item?id=31194347
That would be great if Go provided better performance. With its awful FFI, you have no recourse when you hit its limits other than to rewrite the entire codebase in something else.

As with many things, there's nothing stopping you from just sticking with pure Go if you don't like C toolchains. While C build issues are a valid theoretical concern, in practice I've never had any Python package fail to install because of a C dependency problem that wasn't trivially resolved, nor any Rust project fail to compile because of a C dependency problem at all.

> I've since changed my opinion as it's really nice that one can usually get away without pulling any C dependencies into their dependency tree.

That trophy is owned entirely by the Java ecosystem. Thanks to that, once Loom arrives, basically the whole ecosystem will automagically become reactive-aware.

> The Go default implementation also uses split stacks or something for its goroutines

This has not been true since Go 1.2, back in late 2013.

The fact remains that you need a separate implementation (cgo) if you want to do FFI. It might be something else goroutine-related that blocks FFI in the default Go implementation, but the issue is still there either way.
What do you mean "a separate implementation"? CGo is part of Go, it's not another implementation of Go.
FFI isn't important. It's a niche feature for a superminority of use cases.
> Go's insistence on pretending that complexity doesn't exist

Probably the most accurate and concise summary of my problems with go also.

I am usually unhappy/ worried working in a language or library that pretends the world is simpler than I know it really is. On a good day there is documentation clearly explaining that the maintainers know about the complexity and here's what they've done about that so at least I know; on a bad day it's just shrug emoji.

The article mentions the whole filename thing as an example, and that's one of the first places where I felt I was at home with Rust. It's not unnecessarily complicated but it does force me to acknowledge that yeah, the name of a file might be incoherent nonsense. It's probably a String, but it might not be. I can write code that says "I don't care, we're probably fine" and accept that if it's not fine the code will fail at runtime in a defined way - or I can write code that actually cares about this problem, even if just to explicitly ignore such files as if they didn't exist.

In too many languages the second isn't really an option (which is frustrating if I'd like to write reliable software) or worse, the first isn't an option and so I'm stuck writing endless boilerplate even for a toy or one-shot.

The latter is arguably OK if your language is really just for space rockets and medical implants where failure is not an option. But that's never really how things work out.

Go always seems to be to be designed to be simple for the compiler (which, to be fair, has benefits: fast compilation is useful in a compiled language, to keep code-build-test cycles short) more than the programmer.
Go strives for a balance. It tries to be a fast language without trading off everything else to that end. So it has a GC and really fast builds and it produces machine code that isn't as aggressively optimized as Rust or C++, but it does so much more quickly (as you noted). These are ideal tradeoffs for a huge swath of applications.
I never really understood this reasoning. To me the ideal thing would be a fast debug-compile mode that barely optimizes and a don’t care how slow release mode that uses every possible optimization for the end result.

Rust is plenty interactive with its similar mode of working. Incremental builds are fast.

I am a performance engineer and recommend against this. Optimizations don't really work that well on their own; to get performance you want an ongoing conversation between yourself and the compiler, which you don't get if the compile time is super long.

If you do have a really long running superoptimizer discovering things, then you'd want a way to write that back into the code so you don't need to discover it again.

Also, most of your program should be at -Os because it's not hot code and the important thing is to stop it from disturbing the fast parts. (Or because the aggressive optimizations actually make it slower. Totally possible with fancy ones like autovectorization.)

I've done pet projects in Haskell, Ocaml, Racket, Rust... Now I'm learning Zig... I've worked for years with Java, Python, Javascript/Typescript... Use to work with Z3... Tried plenty of different stuff.

After years my conclusion is that If I want to get a job done I'll choose Golang. Hands down the best productivity programming language nowadays. GC for memory management and productivity, explicit, easy to read, hard to mess up, good performance and efficiency. Get' the job done and really well. End.

I love PL theory. Reading an Idris book an implementing some cool recursive patterns. Building a small project in different languages and compare them... Compilers, type systems and GC papers... But in my experience, the more complexity and "implicitness" a language has to offer, the easier is for "us" to go the wrong way.

I've done all kinds of stuff too, and agree that Go is pretty good to "get things done", especially networking. But I don't know about best. Maybe ten years ago when a static binary was important, but now that everything is deployed as a container, that's off the table and things like Python or Kotlin are equally deployable, but way easier to use. Nowadays, if you _really_ need a single binary, you probably also need it to be tiny. Cramming an entire GC and runtime into the executable doesn't seem much different than building a container to me.
God no not Python. It’s easy to use but it’s something I would only put out there as a duct tape or personal use solution. If I know someone is going to have to look at it after I’m gone, I need something heavily opinionated and without too many syntactic sugar that slows down the refactoring/debugging process.
I've been developing Python professionally for 15 years, including almost a decade of deploying to containers. I think Go is much easier to use (especially in a container environment):

1. Static types make it much easier to read and write code for even a single individual, and the benefit scales superlinearly as the contributor count and code base age increase. Go also has a ton of other tooling which just outclasses Python equivalents for both simplicity and performance (e.g., profiling tools, and even things like gofmt vs black where the former is way faster)

2. Because Python is so slow, even medium sized test suites take a long time to run. You end up having to triage your test suite to keep CI times reasonable. This just isn't a problem in Go (unless you're doing something I/O bound).

3. Python dependency management still sucks. If you want reproducibility, it takes ~30 minutes just to resolve dependencies for relatively small-but-not-toy-sized projects. This obviously kills your CI times, and there aren't great workarounds except to forego dependency management altogether. Go builds are nearly instant in most cases (assuming you have build caching enabled in CI) and still far better than Python builds in the worst cases. Python also depends a ton on C, so cross compilation is basically impossible (whereas it's trivial in Go) and simply building for any non-mainstream platform is going to entail a whole bunch of work (C projects typically make sweeping, undocumented assumptions about their build environments and targets).

4. Being able to make small artifacts is surprisingly important. When your container image is hundreds of megabytes, you feel it in your iteration loop (especially if you're in a "site down" situation and your iteration loop involves rebuilding and deploying containers to production to restore service). It also means your services can't scale up as responsively, and if a container gets bounced (and scheduled onto another node) it implies longer downtime before that container can carry load again. Similarly, rolling back from a bad deploy can be almost instantaneous if your images are small. Go has the advantage here because it can build on scratch images and because it doesn't need to ship the complete source code (native compilation prunes unreachable code, and binary machine code is considerably more compact than unicode source code).

5. If your development environment is Mac or Windows, Docker kind of sucks for Python development because you'll want to mount your source code volumes into the container, but Docker for Mac/Windows runs the container in a Linux VM with a process that marshals filesystem events back and forth over the guest/host boundary consuming virtually all of the CPU allocated to the VM. In Go, you don't mount the volume at all, rather you just build the image from scratch or you rebuild the binary within the image (or outside of the image and copy it in). You can viably use something like `docker-compose build` as part of your iteration loop with Go.

6. Distributing CLIs via container images makes for a crumby end-user experience, and if you don't distribute Python via container images. Something like shiv mostly works, but there might still be dynamic dependencies that users have to include (iirc, we ran into this with graphviz and a few other libs). Go binaries Just Work.

> Cramming an entire GC and runtime into the executable doesn't seem much different than building a container to me.

A Go runtime (which includes the GC) is just a couple of megabytes. Slim python base images are 60mb compressed.

> If you want reproducibility, it takes ~30 minutes just to resolve dependencies for relatively small-but-not-toy-sized projects.

I will be the first to complain about Python packaging, but 30 minutes is far far beyond anything I have experienced.

It seems approximately correct to me. I always do `--no-dependencies` when I have a `pip freeze` output, for exactly this reason.
Idk man. Pipenv. I’ve heard people say similar about poetry, but I’ve also heard people say poetry has improved.
Well put and I agree with most points but

> Go is not a fun language to program in

Not having to think about how something should be done in the most elegant way, instead focus on the problem at hand is a lot of "fun"

Having to write the same code several times with minor changes because of a lack of abstraction is a lot of fun.
I don't know how people can say go "gets out of the way".

Go makes me write dozens of lines of code to do something simple that in an any modern language takes a few.

It doesn't get out of the way, it gets in the way constantly. I'm constantly thinking in any modern language I can just do X, but in Go with its myriad missing features I have to sit and think about how I'm going to do it with just loops and if statements.

It's the exact opposite of getting out the way, don't even get me started on the syntactic verbosity.

> Go makes me write dozens of lines of code to do something simple that in an any modern language takes a few.

"Getting out of the way" doesn't mean it takes fewer keystrokes - it just means that you don't have to think about it / there are no surprises. It took me a while to grok what pythonic code is and looks like, and I feel the bar for Go is even lower. Even if you're browsing an unfamiliar codebase, code is exactly where you expect it to be, and you don't have to ponder on where to make your changes. To me, that is how a language moves out of the way; it fades into the background and you mostly concern yourself with the logic.

Most programmers aren't bottlenecked by keyboard proficiency, but rather by dealing with poor tooling or gratuitously complex programs ("terse" doesn't entail "simple", and very often it's the inverse).
That's a strawman. We're not advocating for terseness in character count (otherwise we'd be using languages like APL and Jelly), but for better abstractions. There are other benefits than character count.

* Having a lot of repetitive code makes it easy to make a mistake when you edit one copy and forget about the others. * A lack of abstraction can obscure intent, making you focus on implementation details. * Having less code overall makes it easier to keep track of it in your head.

True, fun is certainly different for everyone! I also enjoy being able to just focus on a real world problem, but I also programmed Scala professionally for many years, and I found it a lot more fun purely from the point of view of writing code. Writing a really elegant for comprehension or using currying in clever ways to make your code "elegant" was just enjoyable in and of itself, regardless of what problem you were actually trying to solve. Rust is pretty similar to me in that regard.
Same. I use Go for work because that's what the company uses, but I can't imagine coding in it for "fun". Elm + Scala feel much better to me.
Agreed. This was the only nit I was inclined to pick as well. I have a lot of fun writing Go, because it gets out of my way.
I just can't stand taking three lines to unpack a value from a map or to return if error.

Why can't I just say `return if err := somefunc(); err != nil`

It's mega frustrating on top of the lack of generics and other abstractions.

And now that generics are coming about, I'm sure it will take forever until my current project can use them. My current project is in the k8s ecosystem which due to the lack of generics, implemented its own clever but awful type system.

I can't relate. Newline characters have never been burdensome to me, and they aid in visual structure (the control flow is represented by the visual structure of the program, not only for "good data" paths, but also for error paths). My programming problems are usually not related to localized keystroke boilerplate, but rather larger issues of abstraction and data modeling.

> My current project is in the k8s ecosystem which due to the lack of generics, implemented its own clever but awful type system.

The k8s ecosystem's type system is unrelated to generics. It has a concept of user-defined resource types, which means that users can provide an OpenAPI document describing their resource type which Kubernetes will then use to validate new user-provided resources of a given type. From the perspective of the Go compiler, these types are dynamic types--they can't be known at compile time. They aren't a candidate for generics in the host language.

That said, it's often tedious to write a controller for these resource types, but that's because Kubernetes' controller frameworks are really complicated. They remind me of enterprise Java code with gratuitous abstraction. Maybe that abstraction serves some purpose, but it wasn't helping me and I ended up rewriting much of it in more standard Go (I didn't release it because it was prototype code and I didn't want to support it) and it was quite a lot simpler. I don't recall seeing many places where I felt that generics would be a significant improvement, but it's been a while.

Well, if you don't know the structure of a resource ahead of time but know that it has a status.ready, I would think that would be a candidate for a generic? I haven't explored that much yet, but in retrospect I might even be able to convert all objects to a struct that has only status.ready without generics.

I've only been in the ecosystem 6 months, but yeah larger abstractions are difficult too.

I'm not a fan of the lack of sub-classing. I like writing a base class and concrete one, and it's quite difficult in Go unless you want to make everything an interface.

The problem here is that you think "error handling" is somehow different, and probably less important, than normal logic in your codebase. But Go asserts that the "sad path" is just as important as the "happy path".
But programming languages should get on your way while you're doing things wrong. Go does not. To be fare, most mainstream languages do not: I think Rust is the best in this thing, other languages often aren't. But Go is by far the worst of all, because of its striving for "simplicity".
> But programming languages should get on your way while you're doing things wrong. Go does not. To be fare, most mainstream languages do not: I think Rust is the best in this thing, other languages often aren't. But Go is by far the worst of all, because of its striving for "simplicity".

Go typically does get in your way when you're doing things wrong, but yes, I'd like to see Go require return values be dealt with or explicitly ignored. That said, there are linters for this, but in practice it's never been a material problem for me so I haven't bothered to wire one into my project. Over time, I've learned not to be so concerned about issues which are mostly just theoretical--there are enough practical problems to deal with first.

> I'd like to see Go require return values be dealt with or explicitly ignored.

Ever use the return value from fmt.Println?

Seems like the opposite to me. I once tried to set up and run a GRPC service (I can't remember which one) but something it depended on changed and so the codebase I was trying to run basically didn't run anymore. It was f-ing weird that (at the time?) there was no way to lock down deps - that or someone didn't care to? I don't know the language baffles me completely.
There has always been some way to pin dependencies, but historically you had to opt into it via vendor directories and the like; however, as of the last ~4 years, the standard project format manages this for you (the go.mod file pins dependency versions).
> Go is a language that gets out of your way, encourages you to solve your problem

Haven't experienced this yet but I'm a Go noob. I think everything looks easy when you mastered it, I don't think Go is so much easier than JS/Python or even C. Might be easier than Java but Java has so much more community support (e.g Stackoverflow answers) it easily evens out.

I started playing with Go in 2012 when I was doing professional C, C#, C++, Java, and Python. I stuck with it because almost everything was surprisingly easy. For example, I didn't have to learn an obscure DSL just to include dependencies! I didn't have to figure out how to wire together a "test target" in that DSL or evaluate a dozen test frameworks to get unit tests running! I could build and deploy a high-performance HTTP server with a single binary (no external apache/uwsgi/etc web server process)! And often without any third party dependencies at all! And idiomatic code ran 100x faster than Python, and on top of that there was headroom for minor optimizations (pulling allocations out of tight loops, basically). After a bit of experience, it as even easier than Python or JS thanks to static types.

> Might be easier than Java but Java has so much more community support (e.g Stackoverflow answers) it easily evens out.

This was true in the early days, but now Go is extensively covered in Stack Overflow. Of course, there aren't as many Go posts on SO as there are Java posts, but that's because Go is considerably simpler--there's less information to cover.

This just sounds like the same arguments that have been made for C for ages. The complexity of programming is pushed out from the language into the program, the tools, and the programmer’s head. It’s not often that that’s a worthwhile trade-off
Huh, I had sort of found the opposite, that the Go community I had interacted with was more aggro and prone to offense. I'm forming this opinion from the reddit and the discord though, so if there is another community you favor I'd genuinely love to hear about it.
I concur. I was used to a professional tone, then joined discord-go. Showed some of my online code to receive a "shit structure" response by some anime girl avatar youngling. When I started explaining how I don't think docker is the way to go I was met with passive aggressive behavior and plain false responses trying to justify its use. But Go's discord server wasn't to only bad experience. Angular might as well have been called Heil Angular. I worked with it for 2 years and found vue to be more productive. But they didn't want to hear cross worlds experience. Instead they insisted that I had no clue and after a while of back n forth played the "we're the moderators so we're right" card. Then big daddy server owner later stepped up and in the end I was banned, because despite the truth that vue was more productive only their agenda mattered. I have also met hostility in discord's vue server. Insults by by an official name there for saying that it wasn't a good move to require me or anyone joining to provide a phone number for account verification purposes.

All in all discord seem to have the immature unprofessional crowd. It's a gaming chat system after all.

Reddit Go is not as hostile but not very informed either. Although that's not true for all participants.

Compare Reddit to the quality of Go nuts, there is a difference.

But at least Reddit is a more or less open forum, where discord is hidden and a walled pff property.

You can't base your opinion on anything, particularly computer languages, off of your Discord experience, come on.

The Discord demographic is teenagers and young adults, that's the last place where you'd find professional and mature advice about a programming language. I mean, even Reddit is better, and it still is a cesspool.

I've found the rust discord nice and respectful (although I'm also a young adult). I also don't think [edit: PL] reddit is particularly a cesspool, at least compared to HN.
rust discord is at least (semi?)official go discord isn't listed anywhere on official sites
I've had very helpful experiences on Discord (but I don't frequent it). Reddit is Reddit. GitHub (e.g., issue tracker) has been very productive. The mailing list is also productive.
Last I checked there GoNuts on Libera, there's the go nuts mailing list, and there's that Slack. I've seen members of all of the above complain and publicly vye for features and fixes.