Brian Lovin
/
Hacker News

Ask HN: What do you like/dislike about Golang?

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)

Daily Digest email

Get the top HN stories in your inbox every day.

dijit

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.

cratermoon

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.

joshuak

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.

dijit

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. :)

sidlls

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.

liampulles

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.

michele

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"

spiffytech

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?

erik_seaberg

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).

bheadmaster

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.

Winsaucerer

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.

undefined

[deleted]

liampulles

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.

bheadmaster

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.

peterashford

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

jalino23

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

tialaramex

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.

marvinblum

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

ufmace

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.

cratermoon

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.

hinoki

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 <-.

dom96

> 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?

zinodaur

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?)

hinoki

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.

morelisp

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.

fwsgonzo

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.

jolmg

> 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)

joshuak

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.

cratermoon

> 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?

jolmg

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.

pjmlp

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

joshuak

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

jolmg

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

undefined

[deleted]

Winsaucerer

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.

cesarb

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.

majewsky

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.

Daily Digest email

Get the top HN stories in your inbox every day.

Ask HN: What do you like/dislike about Golang? - Hacker News