Hacker News new | past | comments | ask | show | jobs | submit login
Ask HN: What do you like/dislike about Golang?
42 points by nassimsoftware on Nov 26, 2022 | hide | past | favorite | 107 comments
What I like:

- Ecosystem. Very easy to install and use libraries.

- Can compile programs to single executables easily.

- Easy to work with (Very productive) while also running fast.

What I disliked:

- The syntax but I'm starting to like it. (It seems that Go's syntax is an acquired taste for me)




I'm a sysadmin type, so take this with a grain of salt.

Like:

- Compiled and can be run as a scripting language.

- Easy to learn, quite simple to use (even concurrency!)

- Cross compilation is an environment variable setting.

- Fast compilation, though apparently this gets worse with reflection and generics (and it's not that important to me honestly)

Dislike:

- The opinionated build/dependency system, which sort of doesn't work with monorepos unless you do things that "are not recommended" like `insteadOf`

- When deserialising things (JSON for example), an omitted key becomes an empty value. As many will tell you, there is a difference between 0 and Null/None.

- Case based visibility. That feels like the opposite of clear.

- Slice semantics, easy to accidentally override values if you're not careful. (slices are references to arrays, you can reference a subset of a slice which looks like a new slice, but appending to it will overwrite a value in original array, because when you do `var slice_new := slice_old[0:30]`; it's really just a reference to slice_old, not a new slice with the contents from 0-30.


I love case-based visibility. I don't have to look at any declaration beyond the name to know if I can use the identifier outside of the package it's declared in. In most other languages case is just convention and visibility is determined by another keyword.

Yes, I know programmers who came from a Java or C# background who continue to use naming conventions from those languages, to their detriment. I don't blame Go for their problems, though.


I really hated this feature at first, but it's one of my absolute favorite things about Go now. In general Go communicates more information than other programming languages without nearly as much visual clutter. When reading code one rarely needs to check elsewhere to fully understand what is intended.


To each their own, it doesn’t sit well with me and I got used to `grep -rE ‘^pub’ <module>` to lazily get an idea of what is available to me.

Glad it works for you though, it’s not likely to change with my little whinge. :)


I don't even like the concept of visibility--at least as implemented in C++, go, Rust, and a host of others. The whole notion of "getters and setters" just seems like so much pointless boilerplate. Explicitly requiring mutability, as Rust does, is much better for data encapsulation and protection.


nit: One can use pointer values for marking JSON fields as optional, and thus distinguish between the 0 and nil case.

I felt similar to you initially about the difference between 0 and Null/None - I have to say that once I started modelling my APIs so that these two cases could be treated equivalently, I think the quality of my APIs improved.


Not really: it still can't tell the difference between a null value and a missing field which is different when working with APIs as a missing field usually means "don't touch it" while null means "nullify it"


My big complaint with Golang is the way they (don't) handle nulls.

Golang treats null as a memory initialization problem, while ignoring its semantic use.

You still encounter nulls when interacting with external systems, and Golang has inconsistent ways of handling them. From memory (it's been a while):

- Nullable DB columns return a struct with the value, and a boolean for whether the value is actually the value, or a placeholder for null

- Requesting a non-existent query parameter from an inbound HTTP request returns an empty string. That's "fun" to debug.

- Decoding a JSON object with a null value omits the key+value completely. There's no way to tell the key was ever present. This is a problem when you don't control the originator of the JSON.

- I think maybe sometimes null pointers are used?


At 2/3 of every screen, error handling is absurdly noisy. A DSL for wrapping and bubbling up errors (the 99% case) is desperately needed.

The builtin containers just suck. They’re always mutable. Map write conflicts might panic, yet there’s no support for preventing them. They aren’t comparable, which can cause panics elsewhere. You have to pretend slices have move semantics, because copies may or may not share state after an append. They don’t implement any interfaces, so you can’t even replace them or reuse their sugar.

Functions can return tuples, but tuples aren’t first-class, there are no maps or channels of tuples.

No overloading, each type sig needs its own func name and IDE refactoring won’t choose the right one.

Reflection is very limited. No access to types or functions by name. No type or func or arg annotations, just a single string on a struct field.

The GC design is pretty old and people go out of their way to avoid relying on its throughput.

Liked: The memory footprint is pretty small. Parts of the community are starting to rely on code generation to work around the base language (e.g., piles of ignored boilerplate because Mockito can’t be implemented at runtime).


1. Static linking. I'm astonished by how many languages expect the end user to install dependencies/runtime themselves, and something about it feels very impure.

2. The "go" keyword (and the whole goroutine system underneath it). Threads are clunky, somewhat unportable and generally unscalable. Goroutines just feel right.

3. Ability to use multiple major versions of a library. Diamond dependencies are an unsolved problem in many languages I've worked with, but with Go you don't ever even think about them.

4. Implicit interfaces. Having to write "implements X" just to be able to use a type always felt wrong. Implicit interfaces just make sense.

5. The gopher mascot. I just love that little guy.


Good list. Regarding (4) Implicit interfaces -- to me, it's implicit interfaces that feel wrong :) Something having a method with a particular signature doesn't seem to me to be any kind of promise that it's intended to be used for the interface that expects that method.

'implements X', is a clear statement that yes, this method is made just for that specific interface.

But this is just academic. I've never actually hit a situation yet where something just happens to implement an interface by chance, and does something completely unexpected.


Here's what I do:

``` type Doer interface { Do() error }

type CanDoer struct{}

var _ Doer = &CanDoer{} // This ```

If CanDoer does not implement Doer, it will fail to compile, and you can quickly see what you need to do to fix.


I think the question is more in lines of "How do I know that CanDoer does what I expect of Doer to do, semantically?". In which case, it's impossible to know for sure.

For example, I could create a type with a `Read([]byte) (int, error)` method that doesn't conform to the `io.Reader` expected behavior (e.g. reads from the byte array and returns an int if byte array contains a numeric string), yet I'd be able to pass it as an argument to a function that accepts `io.Reader` - the type system won't stop me, but the behavior will be broken.

However, in practice, writing erroneous implementations of common interfaces is uncommon. Naming conventions are really important in Go, so the only way to abuse type system in this way is to abuse the naming conventions, which is already considered bad practice in any sane programming language.


How do you ensure that a language with explicit interfaces implements what the expected behavior is?


It is a kind of honour system, the developer has explicitly stated, yes I want to comply to interface XYZ, instead of doing it by accident and due to copy-paste error passing the wrong one, ensuring a couple of debugging hours tracking down where the error lies in code that compiles without any issues.

Typical debugging problem in dynamic languages.


> the developer has explicitly stated, yes I want to comply to interface XYZ, instead of doing it by accident

To be honest, it's pretty hard to implement an interface by accident. You'd have to specifically implement methods with specific names and type signatures.

If method names + type signatures don't intuitively specify how should implementation behave, then it's either a bad interface or a trivial one.


Yep, this is exactly what I had in mind.


This is, IMO, clear indication of a lack in the language


1. Static linking. I agree with this, coming from JS to learning C, and having difficult time compiling things.

3. Implicit interfaces. I'm in love with this idea when I learned of it. however it wasnt obvious that this should be a thing. if you learned interface from other languages first


Go lacks things I decided I must have, including Sum types and a string type which doesn't just think "string" is a fancy word for "Some bytes". So I no longer use it in anger. As a result these judgements are perhaps a year or two out of date:

I don't like the error handling, and I don't like that they basically punted the hard problem when it comes to data races.

I do like the broad range of stuff in the standard library, and I like how slim its garbage collector and runtime in general feel under use. Start-up time in particular is pretty good for a language with garbage collection.


Without adding another comment about syntax or language specifics, I just like that you can get shit done and stay sane. I have fewer bugs in Go because it's easy to read and because adding unit tests is easy. Deployment is simple because it compiles to a single binary. I don't need to mess with libraries too much because the standard library is almost all I could have asked for. I can show code to other devs, and they understand it even if they don't know the language.

What I don't like is that Rust is also very intriguing to me :D


Likes:

- Big standard lib, including SQL base classes, allowing very consistent DB code. No 6 different ORMs and slightly different DB code for every DB type. Ahem Rust.

- Cross-compiling is easy and consistent

Dislikes:

- Error handling. Done to death, but it direly needs some syntax sugar like Rust's `?` operator. Seems like 3/4 of any function that does anything significant is `if err` branches.

- Nullabilty and the way references work feels like a footgun. Rust feels more clear about what's happening when. Not sure there's much to be done about it now though.


The quality of the standard library, especially net, time, and to a lesser extent, strings.

The tooling, including the built-in testing package and test tool, and go doc. Also vet and fmt, if for no other reason than it precludes most arguments over braces and tabs vs spaces. I don't have cause to use it much, but pprof is a dream.

I like the idea of the go module system, and the fact that dependency management is part of the language ecosystem. It's still a bit clunky, especially if you end up, like I did, stuck with a system that has lots of libraries with poorly-thought out dependency relationships.

interfaces, especially single-method ones. But also the fact that your code doesn't have to declare it's implementing an interface, and has no dependencies on the interface declaration itself.


As someone who likes async/await/coroutines, I really miss them in Go. Channels are the GOTO of synchronisation, they lead to confusing spaghetti code because there is no structure.

Go is really nice to read (once you get used to the fact that half the lines of code in any function are for error handling) unless it uses a lot of channels. Then you need to take great care to understand every <-.


> As someone who likes async/await/coroutines, I really miss them in Go

Interesting. Most complain about languages with async/await and say they want goroutines. What is it about async/await that you prefer over Go's approach?


Not OP, but I am also in the camp of "like goroutines, not a fan of channels". Low cost threads are great - but channels can represent so many intermediate states, especially when error handling is involved. I wish there was a simpler default for coordinating goroutines

(Do we add an error field to the struct we send through a channel? Do we close the channel on error, or only on success? Do we make a separate channel to send errors on - and if we close that one it means for sure that there were no errors? If we select on the two dependent channels, do we have to make sure that they are in different sequential selects, so that we don't go out-of-order or deadlock?)


Async/await allows you to be much more explicit about dependencies. I don’t care what thread executes something, but I do care that e.g. these three network requests happen in parallel. With async/await I can explicitly use an ‘await Promise.all(a, b, c)’ where Go would need a new channel and goroutines, etc. all being implicit, so it’s hard for the reader to know what the intent is, and it’s error prone, who knows if you properly clean everything up.


This is a weird example as Promise.all and Promise.race are what are pretty easy to write in Go:

Promise.all is trivial:

    return <-a, <-b, <-c
Promise.race is a little longer:

    select {
        case x := <-a: return x
        case x := <-b: return x
        case x := <-c: return x
    }
What Go doesn't do very well compared to promises is composing; composing channels, especially with error returns, is usually tricky. But async/await are also bad at this. Passing around the actual promise/futures objects is the best way, which is for some reason discouraged today in favor of await/async noise in most cases.


For me Go is unique in that it can produce a fully static binary that can fetch from a HTTPS endpoint. It also supports many architectures. On top of that the package system and it's just way more convenient to use than other languages. Easy to learn, easy to put somewhere in practice.

Not every program has to be 100% safe and delivering 2M req/s. Sometimes you just want to build a program once and ship it.

What other language has that? Go isn't perfect though. It's startup sequence is long, the API towards C (and other languages) is quite bad, and if you build a fibonacci program and compare it with C it's 3x slower despite requiring no allocations whatsoever.


> For me Go is unique in that it can produce a fully static binary that can fetch from a HTTPS endpoint.

What do you mean? C, C++, D, Rust, Haskell, and Common Lisp seem to be capable of static linking. (Admittedly Common Lisp seems to require a fork of SBCL that was written about last year, but still)


In Go that quote is literally true. If you do nothing on a new computer but download Go you can immediately build a statically linked application that can fetch from an https endpoint. There are no implied additional steps.

I don't believe this can be said of the other languages. I know that C, C++ and Rust do not include https in their standard libraries, so while they can be made to compile statically and use a library that provides https functionality, those are additional steps that must be taken by the developer, and it is the responsibility of the developer to choose the correct source and version of the https library to use. This will also include understanding and setting any additional compiler flags that the library may require, setting any optional defines or other library configuration settings and making the appropriate changes for every platform they wish to build for.

Go requires none of this.


> that can fetch from a HTTPS endpoint.

Seems like a lot of replies are glossing over that clause. How many of those other languages can do that in a fully static binary that runs on (almost) any system that can read and launch that executable?


It's glossed over because it's trivial, isn't it? Just statically link libcurl through a binding. And at least Haskell also has Haskell-implemented HTTP and TLS libraries. There's even options.


Then why not assume it is the static linking that is trivial? Isolated each item is trivial in some context, its the exclusive set that is non-trivial, or at least uncommon.

The fact https is included in the standard library means that you can give a new user a hello world tutorial that includes producing a web server. It's a huge boon to productivity in a programming language to have a default path for such libraries.

I also work in C++, and it is infuriating the amount of time that must be spent sorting out the correct libraries for all the various aspects of an application one is not inclined to write themselves. People who don't fully grok the Go ecosystem overlook this cost when they claim that you can do the same thing in some other language. What they are missing in the subtext is the fundamental quality of life improvement.


> Isolated each item is trivial in some context, its the exclusive set that is non-trivial, or at least uncommon.

I'm not just taking it in isolation for no reason. If you have static linking, you basically have HTTPS by consequence.

> The fact https is included in the standard library means that you can give a new user a hello world tutorial that includes producing a web server. It's a huge boon to productivity in a programming language to have a default path for such libraries.

I think you're taking our discussion as if it were about if the language is cool or not. I never argued against that.


> I'm not just taking it in isolation for no reason. If you have static linking, you basically have HTTPS by consequence.

Can you clarify what you mean by this because to me there is literally nothing about static linking that implies https as a consequence. The point about Go is centrally not that it uniquely has access to an https library.

The point is that it is included. This may not at first appear all that noteworthy, but this is a substantive quality of life improvement. The standard library not only provides a vary large set of common functionality it is packaged with the distribution, works on all the platforms supported by Go without user intervention, and is bound in lock step to the release version of the compiler.


Static linking predates dynamic linking by several decades, hardly unique.


Wait, what prior programming language is compiled, can be statically linked, and has an https implementation in it's standard library?


fwsgonzo never said anything about it being in the standard library.


The convenience of building network clients in Go is heavily implied. Strictly you can say that assembly code can make standalone binary HTTPS endpoints, but that would be a bit silly.

To be sure you might write such a program in assembly, but you’d be doing it for fun or aesthetics. No engineering manager would commission such a thing.

Though of course this is inviting the trading maniacs to tell stories of hyper optimized clients…


HTTP doesn’t need to be in stdlib. Java and Python each had one but everyone long since switched to third-party reimplementations with better APIs. And HTTP will probably be replaced over the next twenty years, like FTP and Sun RPC and CORBA before it.


Ok, but it's not either Go or assembly. There's other languages. I listed 6 in a close-by comment. I wonder if Go's much more convenient at network clients than, say, Haskell or Common Lisp.


Well that's not on him man. You are the one calling him out for being unaware of static linking, when you don't even seem to understand the full set of pros he listed in his first sentience.


You're not replying to the same person. In a separate comment, I asked for a clarification on that specific part of their comment. The other pros, they're not part of what I asked about.

In the comment I replied to you, I'm just stating that you're bringing up something that's not in discussion.


Sorry for the misattribution, my mistake. Nevertheless, the fact remains that https is a part of the standard library and one of the elements that the op finds unique about Go. It is unambiguously part of the conversation, and the ostensibly negative comment that focuses on static linking is missing the point.

But I didn't even make the assumption that I was right on that understanding. I simply asked for more detail. Which was so that either I would learn something new, or the parent would.


> https is a part of the standard library and one of the elements that the op finds unique about Go.

Where do they say that it being part of the standard library is unique? I don't even see the words "standard library" mentioned.

EDIT: Look, this back and forth seems kind of pointless. This started with a statement about Go being unique for being able to be statically linked and talk HTTPS. If a language can be linked to compiled libraries, static or not, it's surely able to at least use libcurl, so I and the other commenter you replied to took it as Go is unique for being able to link statically. I assumed they meant, ignoring C/C++, static linking is rare, which I found interesting. I found other languages and asked for clarification. That's all.


I just addressed this question more directly on your other comment.

I do not agree that it is incumbent upon a speaker to anticipate the listeners knowledge, so I do not think it is a reasonable expectation that every qualifier be included. It's simply not practical. But I do think this is an interesting conversation you bring up.


Like:

- Concurrency. Can shoot yourself in the foot still, but it's overall pretty simple to get started and use.

- Small language surface. Fairly easy to keep most or all of it in your head.

- Good ecosystem, and easy to pull in libraries.

- Good standard library that does a lot out of the box.

- Easy for new people to get started with it.

- Compiles to a single static binary.

Dislike:

- Cluttering of my code with boilerplate error returns (a common criticism!).

- Can't stop consumers of a library from doing the wrong thing. E.g., ensuring that someone always initialises an internal map of a new struct instances is challenging, with tradeoffs for each option.

- In line with previous, overall lacking the language tools to enforce correct usage or behaviour.

- Lots of footguns.


It's been a long time since I last looked at golang, but the first time I met it, I instantly disliked the way it imposed its own idea of filesystem organization onto the user. I wanted to run a Heartbleed checker, and instead of being able to clone the repository into a new subdirectory inside my ~/projects and compiling and running from there, like I would do for projects written in any other language, the go compiler required that the project was cloned into a global centralized path, where all projects written in golang would end up, mixed together without any separation.


The requirement to work in GOPATH has been dropped around 2018. Nowadays, by default Go is running in module mode which works pretty much like in all other languages with builtin module management.


Like: get things done mentality, green threads, duck typing, default formatter.

Dislike: still no easy way to dynamically create mocks -maybe some brave soul will try to solve it after generics :)- I don't like code generation or manual mocking, we have a large codebase, 1000+ file and so much noise in the repo because of generated mocks. And yes we are fan of unit tests and aiming 100% coverage on business logic.


What I like,

It is a nice language had it been released in the mid-90's, following the footsteps of Oberon and Limbo.

What I dislike,

Being designed a decade later ignoring everything that happened in mainstream computing since Oberon and Limbo came to be, then adopting features that weren't properly backed in from the get go.


Can you elaborate more on what was ignored?

For one, I think it's mind blowing that they didn't have a good system for dependency management in place from the get-go. Things are pretty good now with go modules but it's like they never even heard of languages like Python and the unholy mess that package management was (and still is to some extent) with Python.


Not mind-blowing at all. It's originally a language designed by Google, for Google, and Google famously uses a monorepo for most of their code. If you have a monorepo, package management is a non-issue.


The whole generics drama.

The const/type declaration dance to this day, to declare enumerations, even Algol could do better.

Error handling based on comparing strings, to find out the real cause.


> Error handling based on comparing strings, to find out the real cause.

I'm not a fan of Go, at all, but credit where due: they did make this slightly better with error wrapping (https://pkg.go.dev/errors#Is). Of course, this doesn't work unless errors are wrapped, instead of blindly concatenated, forcing you to do string matching more often than not. :)


Like:

- Compiles to a static binary that's relatively small compared to something like .NET AOT.

- Structural typing.

- No semicolons.

Dislike:

- Lack of exceptions. I can understand wanting to avoid exceptions for safety critical software like avionics, but for most application programming, requiring manual error handling for every function call is cumbersome and unnecessary.

- Too verbose / lacking features. For example, there's not a short syntax for lambda expressions, like the => operator in C# or JavaScript.

- Public / private access modifiers at the package level rather than the class level (there are no classes).

- Language development is driven primarily by Google, who has a history of killing products.


Pro:

- Community. Pragmatic, high signal to noise ratio in online resources, little magic, sympathetic to the needs of production operations. Consider for example httptrace [0]. This kind of introspection isn't even normally possible in many languages and their library ecosystems. In Go world, people care about this stuff.

- Standard library. High quality and flexible packages for stuff like web servers and clients, TLS certificate manipulation, SQL, various data encodings, etc.

- AST package, strong first-party support for manipulating Go source files.

- Table driven testing norm is very cool in certain circumstances.

Con:

- Verbosity. Something like making an HTTP request is ~7 different operations, each with their own error branches.

- The tedium of meeting coverage mandates. Having to code-generate tons of mocks and type out very elaborate test tables for what feels more like busywork than genuine confidence-building. Partly this is my employer's fault for having high coverage standards though.

- Despite its apparent simplicity, there are significant footguns lying around slices, loop variable captures, channel deadlocks, etc.

- The Go community's solution to a lot of language shortcomings (verbosity, type system, etc) is code generation. For example, we have an internal framework for parallel task graph execution, which is much safer and cleaner than manually managing channels and goroutines. But this kind of thing cannot be expressed as a regular library like it would be in other languages.

- Despite the prevalance of code generation, Go does not quite have a macro system, leading to third-party build systems like Bazel in companies that do a lot of code generation with their Go. This then leads to rough edges with the other tools' handling of "de-materialized" generated code and the need for stuff like GOPACKAGESDRIVER for proper editor support. Editor and debugger support for generated code, the way things stand, can be rough.

- Certain syntax rules such as trailing commas and unused variables/imports (especially when commenting stuff out temporarily to debug something!) can get on my nerves.

[0] https://pkg.go.dev/net/http/httptrace@go1.19.3


Like: The package and build system is amazing and just works. Simple, yet intuitive and powerful with the introduction of modules. It's hard to quantify the number of hours I've saved over other tools hunting down build/linker/dependency management issues.

Dislike: Error handling feels extremely verbose. I like the design choice to return errors explicitly, but it feels like there needs to be a return-if-error syntactic sugar introduced to avoid the endless if err != nil ... return err peppered everywhere.


> Like: The package and build system is amazing and just works.

I don't have much experience with go, but that's not at all my experience. I had the compiler panic on me for daring to run "go get", as described here: https://github.com/golang/go/issues/47979

This issue has now been closed, but there are several others open with the same symptoms. The response by a golang maintainer was:

> It seems likely that there is still a bug here, but the code is complex enough that we're unlikely to find it by just reading through the code.

Anecdotally, i've tried to fork the imported module to remove some dependencies i was not using, and failed miserably, as explained here: https://github.com/ooni/probe-cli/pull/986#issuecomment-1327...

I have experience with C/Rust import systems and i can say with 100% confidence that golang's module system is far from amazing.


Having worked with Go from the days of vendoring, to Glide, to Dep, it is really amazing to see Go turn its greatest weakness into one of its greatest strengths and most-loved features. It was worth the wait. Here’s hoping the rest of my gripes follow a similar arc.


Aside: has anyone read “100 Go Mistakes and How To Avoid Them” [1] and would care to share their takeaways?

After getting back to it after quite some years I’m still conflicted about the language and its tooling. It’s a frustratingly (and seemingly) primitive/simple language but also a very productive one. I personally don’t love golang but it’s a great tool “for the common programmer / worker”.

The unix philosophy is baked in without the excitement (to me). That’s OK.

Also the stdlib has similarly great / lacking characteristics. It’s strong but then often missing basic functionality that you just don’t want to maintain yourself any more (e.g. try to do a unix like sort / unique on a slice of strings - afaik only lexicographical sorting in there).

I would say that the main driver for my rekindled interest in the language stems from its current Lingua-Franca aspects for “higher level systems programming”. I’m thinking Kubernetes and Terraform here for example.

In many ways but especially in this one it’s similar to Python actually - which I also don’t love at all - but which FWIW found its niche as the current go-to scientific computing and machine learning language / ecosystem.

[1] https://www.manning.com/books/100-go-mistakes-and-how-to-avo...


I think it's a very pragmatic language and I like that.

I hate the error handling.

I dislike implicit interfaces - they make refactoring harder. The existence of interface assertions is an anti pattern.

The standard library leaves a lot to be desired. It's very much not a "batteries included" language.

The go community are super defensive and resistent to criticism. So many times I see people ask reasonable questions about something lacking g in go only to be told that it's their fault for wanting it.

I may sound negative but I actually rather like go. But no language is perfect, obvs.


LIke:

- Decent speed of compiling and execution.

- Expressiveness.

- I like the struct/receiver/interface interplay to create an analog of classes.

- Nice standard library.

- Cross compilation.

Things I wish for:

- Ternary operator.

- Something a bit closer to inheritance. Embedding structs in other structs fools you into thinking there's inheritance, but you quickly learn that it doesn't work the way you think it will.

- At the same time, fully implementing a functional programming paradigm isn't particularly easy either.

But all in all, I'm a big fan.


> LIke: - Expressiveness.

Isn't Go deliberately not very expressive?


The answer to both is "it's boring".


So true :D


Like: standard library and readability. The "If it ain't broke, don't fix it" philosophy that I miss from the earlier Python days.

Dislike: awkward error handling and testing / mocking


Go is the best.

Go’s culture of minimalism, pragmatism, and just get things done is also amazing.

What could be better:

- Even faster runtime.

- Even more efficient garbage collector.

- Sum types would have been nice to have.

- Structured logging would be great to have.

- Structured error would be nice as well.

- Cgo could be better.

- Memory arena would be great for database applications.

- Buffered channel is not as useful as I thought.

- Better channel performance would be nice.

- Closing a channel is not safe and easy to make a mistake doing it.

- Embedding could be improved.

- Standard library needs a lot more data structures.

- It needs a tuple data structure so that users can return (result, result2, error). If this exists, we can create a nice chainable libraries.

- gopls automatically generating test struct as a second implementation of an interface would be super nice to have.


Likes:

- The build/test tool is pretty good

- Opinionated formatting/style is nice (even if I disagree with some choices, it removes a huge organizational bikeshedding problem)

- Explicit error handling

Dislikes:

- The generics implementation leaves a lot to be desired

- Structural typing: may as well be a dynamically typed language (I know it’s not that bad, but sometimes it feels like it)

- The terrible implementation of nested/embedded errors and lack of support for structured, pattern match inspection of error return values tend to make error handling unnecessarily verbose

- The insane use of interfaces literally everywhere, and the tendency for hidden/unexpected use of “reflect” leading to funky performance issues


I dislike:

- Duck Typed interfaces

- Unused variables are compiler errors

- Lack of Ternary operator

- Variable short declaration (:=)

- Using case for exported names instead of access modifier


Like:

- Ecosystem

- Static linking

- goroutines

- small and light runtime

- fast compilation speed

Dislike:

- missing shorthand lambda functions, making functional programming very painful

- typed nils

- billion dollar mistake (lack of non-nullable types)

- implicit/automatic copying on value types. Value types as a whole need to be re-thought.

- too much error handling boilerplate

- unused variables are compiler errors

- struct init not exhaustive


I forgot about starting with nil and ending up with an interface where every method will immediately panic, yet doesn’t equal nil because it’s carrying a type around. Reminds me of the HHGG box containing “no tea: just like the tea professional hitchhikers don’t carry!”


The simplicity is nice. It's one of the more readable languages. I particularly like that reflection has sensible limitations, so you can't produce the ridiculous frameworks that appear in Java and friends.

I wish channels and goroutines were more strict, so that you could have some basic assurance no race conditions can be present.

Defer is very convenient, which is a problem, as it is a source of bugs. It's usually used to ensure file-like objects get closed, but generally those return important errors when closed. A simple "defer file.Close()" will silently ignore the errors generated.


Like:

-The go AST package was a pleasure to use

Dislike:

- Boilerplate error handling

- Excessive verbosity

- Multiple return not being of a type

- Duck Typed interfaces and the empty interface

- Go generate that makes verbosity worse

- Case-sensitive namespacing that leads to hunts for all the

instances

- Namespacing in modules that leads very large files

- Shadowing rules that let you shoot yourself in the

- Rules around =, := and var

- Slice tricks and for loops try to encompass iteration instead of something like list comprehensions

- Nullability and default values

- Short variable names to the point of being meaningless even in the standard library

- The go community's seeming insistence on a singular or idiomatic way to do things

- The standard library is missing basics

Edit: Added some formatting.


Languages must choose tradeoffs, and I think Go gears its tradeoffs towards large projects.

Generally the larger a project becomes, the longer it takes to add features and fix bugs - so to combat this Go favors quick compile times, easy monorepos, simple tooling, and most of all: very consistent code. Go's limited syntax is a feature, not a bug.

Syntax sugar in a language allows for writing code quickly, but it (arguably) opens up more opportunities for bad code - e.g. using language features inappropriately, mixing the "sugar" approach with the "manual" approach in the same codebase, being terse and hard to read, etc. If most of a developer's interaction with code is spent reading rather than writing, then it makes sense to value quick to read but long to write code. Go's philosophy of there being "one way" to do things (arguably) achieves that.

Having a very transparent control flow (including treating errors as simply values) is in line with that. Yes errors pollute one's code - I would like an operator for that, but I'm glad the error conditions are being represented clearly.

Go is perhaps not a good language if you want to get an MVP going quickly, or write something with a performance focus.

Disclaimer: I love Go. I taught myself Go while I was working as a Java dev and it was a big factor in me finding a new job.


Having to use reflection and the interface{} (now 'any') escape hatch to see what fields are in an unknown JSON object "the pragmatic way"


Like:

- `go generate` for embedding resources into the executable. So intuitive and beautiful.

- Strict curating. It's the kind of language that things don't get into unless they're the right solution.

- Performance! Python was my favorite language before Go got sufficiently mature. It was refreshing to have that much efficiency coming from Python.

- `if err != nil {`, dammit!

- Imports referring to the location where the code can be got. `import "github.com/fogleman/gg"` for instance.

- `go fmt`. Nuff said.

- Tabs as the standard indentation character.

- `fallthrough`

- `init()`

Dislike:

- nil. I wish nil had a smller role in the language. It's an agreed best practice in Go that a zero value should be a ready to use thing. I feel like that would be easier to do (and more true of the core language) if the zero value of a slice/map was an empty slice/map, the zero value of a pointer was a pointer to a zero value of the type it points to, etc. Maybe this can't be done for all types (function and interface types, for instance), and maybe there are good reasons for the way it is that I'm not smart enough to realize, but if it could be done for some types, it would be nice.

Things I Pretty Much Eschew:

- Generics are limited. Maybe I'm missing something, but I doubt I'll be using generics as they are very extensively at all. If we could make generic types that would be something I could see myself using more.

- Embedding. It's rare I find a use for that.

- `defer`, honestly.


> If we could make generic types

What do you mean by this? Methods?


Generics as currently implemented in go are so limited that you may as well stick with interfaces for anything but trivial boilerplate or textbook data structure/algorithm implementations.

For example, it's currently impossible to define type constraints with user-defined interfaces embedded:

  ```
  // this is legal
  type MyConstraint interface {
    int64 | float
  }

  type SomeAPI interface {
    AMethod() error
  }

  // Nope
  type CantHaveThisConstraint interface {
    int64 | float | SomeAPI
  }

  // NOPE
  func SomeGeneric[T CantHaveThisConstraint](...) {
  }
  ```


It's true that doesn't work because you can't mix types with methods and types with no methods. But that's a far cry from "Go doesn't have generic types" or "may as well stick with interfaces for anything but trivial boilerplate".


I dislike job descriptions. For some reason, most Golang positions are actually DevOps positions (Kubernetes, AWS, etc...).


It's a very practical language, and I like its affinity for static binaries.

A downside is Google's approach to handling modules: https://drewdevault.com/2021/08/06/goproxy-breaks-go.html


This seems to only be a complaint about a particular (granted, default) proxy for downloading modules, and no criticism of the actual module system (which does not depend on a proxy) nor the idea of a proxy (which is pretty easy to run, especially when compared to most other languages' package repositories).

As to the criticism, if a public module isn't playing well with the default proxy, it's a good sign I don't want to use it to begin with.


> about a particular (granted, default) proxy

Yeah, but defaults matter. That's one of Go's big selling points, usually.

> As to the criticism, if a public module isn't playing well with the default proxy, it's a good sign I don't want to use it to begin with.

The concern raised here is more that things only work because the proxy masks problems. Also alluded to is that said default proxy is kind of awful to its upstreams, unnecessarily pulling things and burning bandwidth.


> The concern raised here is more that things only work because the proxy masks problems.

But making the modules available in the face of upstream breakage is a point of the proxy. I can still build when GitHub is down. I can still get the version of the source I expected when upstream rebases or retags. Those are reasonable defaults. And with one env var I can also turn that off and check that the upstream is still as I expect.

> Also alluded to is that said default proxy is kind of awful to its upstreams

This is debatable; sr.ht seems to want to host a lot of go repos but not have people pull a lot of go repos. I'm not sure that's a case worth catering too.


Below are my current feelings having worked with it as my primary language over the last 16 months. My previous 7 years of development focused on Java, C++, Python, and JavaScript.

Like:

- Standard Tooling: formatting coverage, dependencies, BIN install, versioning, vendoring all done in Go CLI

- Opinionated, minimal design: often I feel like there are fewer ways to do things in Go than in other languages.

- Readability: fewer operators and minimal language design make it feel easier to ramp up and read most go programs

Dislike:

- Hard to master: some elements are very unintuitive coming from other languages (interfaces). I feel like a lot of content on go.dev is out of date (Effective Go). The Google Style guide is helping to light a path and when they publish Go Tips I think it will get better.

- Too Minimal: in some cases it feels like Go went too far in not building language features (no set, use map[$TYPE]bool instead or map[$TYPE]struct{} for more efficiency but less readable imo)


Lack of a set type is just bizarre to me


Like:

Errors as values. Would I prefer Results/Options baked into the language? Yes. Am I glad we don’t have to deal with exceptions? God yes.

Stdlib, the ecosystem, and globally the mentality of the community regarding dependencies.

Mostly self contained binary that’s easy to (cross) compile and deploy

Performance

Dislike:

Lack of ternary expressions

Lack of some standard interfaces (Iterable is a big one)


Not too many complaints. I like it much better than Java backend.

Go and Typescript are now my go to for everything I think.


curious if you prefer Typescript or Go for backend development. My friend group told me they prefer Typescript because of shared code with client and ease of hiring.


I have a nodejs+typescript backend stuck in a limbo state because of the whole esm modules fiasco. I have outdated npm modules I can't upgrade because they are ESM only, and I have old modules that will likely never update.

It's to the point where I will only be using golang for backends moving forward.


I’ve heard the shared code argument before, but in practice the only shared code I’ve really ever seen used is shared type definitions. Now shared type definitions are huge, but they’re a solved problem using JSON schema, GraphQL, or some other interface definition language that don’t necessarily require the same language in the front end and backend.


I migrated my scrapers from Python + BeautifulSoup to Go + Colly and it was a pleasure: the new programs are impressive fast (1/3 of the time) and managing the concurrency and parallelism is a lot easier in Go that messing up with asyncio. Even the dependency management is a pleasure, no need for virtualenv or other tools.

If there is something that I don't like it's the serialize/deserialize of JSON objects: you need to know the structure of JSON in advance and define in your struct. When you are scraping an unofficial API and this change silently, this can be a pain.


> you need to know the structure of JSON in advance and define in your struct

You can unmarshal to map[string]any if it’s of any help. You have to cast a lot but it’s sometimes useful.


Thank you for the suggestion, but yes, I need to cast a lot.


Like: 1. Expansive Stdlib: it’s great to know there’s a working library I can rely on that will have long term investment for most things 2. Compile times are fast 3. Readable libraries. It’s pretty easy to folllow go library code Dislike: 1. Interface{} and gotos. They are easily avoidable in production code, but they open up some weaknesses in working with third parties. 2. Error handling. It should be a compile error not to handle a Retuned error without an _.


Like:

- Simple concise syntax - Great std lib - Most of the times it's easy to do the right thing

Dislike:

- No concept of NULL values and what it means especially when dealing with JSON - Limited handling of TCP connections life-cycle

I'll add something which is not a language issue per se but I had to deal with a lot: often new devs to Go will try to force certain mental models acquired with other languages/frameworks onto Go and that makes for some terrible Go code.


What I like:

- Easy to deploy

- Short learning curve

- goroutines

What I don't like:

- c bindings are not the best

- Error handling code gets old, but at least it's always consistent and never magical


Go/XML dislikes:

XML decoding into structs is an annotational nightmare.

XML namespaces are broken and getting no development love.

Go/XML likes:

Otherwise quite OK.


Don't like error handling taking multiple lines (https://www.youtube.com/watch?v=raJGZaIPhOc)

Like everything else.


Love: - Ecosystem - Clean code - Single executable file

Dislike: - Pointers -- maybe I am still getting the hang of it. But I feel like pointers throw off a lot of beginner programmers. Would love some practical advice here


> pointers

Stick with pass-by-value and non-pointer receivers. Use pointers only if you have to.


I worked with one team who deliberately chose pointer receivers for everything. Their reasoning? The compiler can't know if the receiver for the call you are making will be nil at run time, so it doesn't complain.

Yes, they literally chose to subject themselves to runtime panics to silence the compiler.


here’s a good rationale about when to use pointers: https://stackoverflow.com/a/23551970/255463


I'm new to golang, but i've had bad experiences with it so far compared to other languages.

- method declarations are hard to grep for because the related struct appears before the method name (like "grep 'func String'" will not find "func (s Struct) String"), and because a module's types/functions can be declared across several files

- many pointer-related footguns, many others have mentioned the nil pointer exception issue, but as a newcomer to golang i found it confusing that i could accept value params in methods/functions and mutate those params... only to have those mutations silently discarded because they were a copy of the original data to start with

- do you even concurrency?! i ran into a deadlock by using the stdlib in a very simple way, in a sequential manner, in a single-threaded program. i still have no clue why that is, and the program maintainers don't either. i'm probably doing it all wrong, but it was so easy to shoot myself in the foot. If someone more experienced wants to teach me what i did wrong, here's the code: https://github.com/ooni/probe-cli/pull/989#discussion_r10325...

- the module system is entirely broken: i've had the go command panic* on me for simply using "go get"... the problem is old and well-known, and has quite a few tickets open on the golang bug tracker: https://github.com/golang/go/issues/47979

- the attributes exposed by marshaling libraries are different from one library to the next... i mean that's a problem in most languages but after trying out serde.rs briefly i feel like that should be the way in every language to just standardize those fields

- golang is not structured for commenting out parts of the code to test something quickly: unused imports/variables is a compilation error, and apart from naming a variable "_" there is no way to ignore its usage (using "_" as a prefix like in rust would be an awesome addition)

- struct declaration and instantiation has inconsistent syntax: why would i use comma after a field instantiation but not after a field declaration?

- the difference between ":=" and "=" is clunky... as you refactor code you end up having a lot of this variable doesn't exist, or this variable already exists kind of error... i understand the need for proper variable instantiation and type declaration in general, however ":=" does neither as you require an entirely different syntax (var) for explicit types, and golang is very happy with passing around nil/empty values

- publicity based on case makes it super hard to decide later whether you want a method/field to be public because you need to change the entire API, instead of simply adding a keyword

- no standard way to define default fields for a struct? there may be one but across all libraries i've used so far there was weird NewStruct() or NewDefaultConfigStruct() methods which were hard to grep for in the docs/code

All in all, it's not the worst programming language i've used. JavaScript is a lot worse. But from a week of working with it full-time, i feel like go has massively failed at being a better C or a better Python... it feels to me like it combined the failure modes from both ecosystems into a new rather incoherent language.

If you're looking for quick scripting, i would still recommend Python or PHP which in my humble opinion have much better developer UX. If you're looking for serious systems programming, i would recommend Ocaml, Rust, or plain old C, all three of which have amazing tooling. It may be weird to recommend C as an alternative to golang, because C is so low-level it's easy to shoot yourself in the foot... but that's the thing, C does not try to pretend to be a safer high-level language, it gives you raw tooling. golang tries to be higher-level but i think it failed at that.

Sorry for the rant, i don't mean to be rude. I just don't understand the hype around golang.


You'd still recommend PHP for scripting? The developer ux of php is miles behind compared to Go, though I suppose it depends what we mean by it.

Composer is clunky as hell and Concurrent scripts are just a big fat no in PHP. I need concurrency a lot in my line of work yet my colleagues never wrote concurrent code in their life so they have no idea what they're missing out on.




Consider applying for YC's Spring batch! Applications are open till Feb 11.

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: