August 17th, 2024

An unordered list of things I miss in Go

The blog post highlights features missing in Go, such as ordered maps, default arguments, and improved nullability, suggesting these enhancements could benefit the language despite requiring significant revisions.

Read original articleLink Icon
An unordered list of things I miss in Go

The blog post discusses various features that the author misses in the Go programming language, highlighting areas for potential improvement. The author appreciates Go's qualities but believes it could benefit from enhancements. Key points include the absence of ordered maps in the standard library, which would allow for consistent iteration order based on insertion. The author notes that while third-party implementations exist, having such a feature in the standard library would reduce friction for developers. Another point raised is the lack of keyword and default arguments in function declarations, which could simplify function calls and improve API design. The author cites Python's handling of default arguments as a beneficial feature that Go currently lacks. Lastly, the post addresses the issue of nullability, suggesting that while Go's use of zero values mitigates some problems associated with nil values, the language could still benefit from a more robust approach to nullability, similar to what is seen in other languages. The author concludes that some of these changes would require significant revisions to the language.

- The author misses ordered maps in Go's standard library for consistent iteration.

- Default and keyword arguments in function declarations are seen as beneficial features missing in Go.

- The author highlights the need for improved handling of nullability in Go.

- The post suggests that some proposed changes would require major revisions to the language.

Link Icon 34 comments
By @SkiFire13 - 2 months
> While I understand the reason for this (i.e.: to avoid developers relying in a specific iteration order), I still find it weird, and I think this is something unique for Go.

Rust's `HashMap` and `HashSet` also do the same with the default hasher (you can plug your own deterministic hasher though). The reason for this choice, which I think also applies to Go, is to be resistant against HashDOS attacks, which can lead to hashmap lookups becoming O(n) on average. This is especially important for maps that will contain data coming from untrusted sources, like data submitted in a GET/POST request.

I do agree though that an ordered map would nicely solve the issue.

By @coldtea - 2 months
>While I understand the reason for this (i.e.: to avoid developers relying in a specific iteration order), I still find it weird, and I think this is something unique for Go. This decision means that even if you don't care about a specific order, you will still need to sort the map before doing something else if you want reproducibility

Even if they didn't randomize, unless they also explicitly guaranteed stable map order across versions, you DO need to sort the map if you want reproducibility.

Because if you relied on it being conveniently stable within the same Go version, with no guarantees, your program would still be broken (reproducibility wise), if they changed the hashmap implementation.

By @garyrob - about 2 months
Borgo is an interesting attempt to address some of these issues. I would love it to get real traction.

https://github.com/borgo-lang/borgo

By @kitd - about 2 months
I disagree with having the ordering embedded into the map implementation. That imposes an unnecessary performance overhead to support a small subset of use cases.

I think what the author requires is iterating over a sorted list of keys. That is pretty easy to implement using the standard library, and imposes the performance penalty only when it is needed.

By @akdor1154 - about 2 months
Nillability is the biggest thing that drives me to write 'unidiomatic go': there are a few Optional libs around, they work ok.

I write with the following rule: if a pointer is passed, it shouldn't be nil. If it might be nil, code it as an Optional<> instead.

Un-golike but works great.

By @cempaka - 2 months
> While I understand the reason for this (i.e.: to avoid developers relying in a specific iteration order), I still find it weird, and I think this is something unique for Go.

Haha well, fun fact, Java did this as well after a bunch of code was broken by a JDK upgrade which changed HashMap iteration order that programmers had been relying upon. Java does at least have ordered maps in the standard lib though. IMO it is a questionable decision to spend CPU resources on randomization in order to mollycoddle programmers with a flawed understanding of an API like this, but then again I'm not the one who gets the backlash when their stuff breaks.

Also, on the subject of nullability, while JSR305 may be considered dead, there's still pretty active work on the Java nullability question both from the angle of tooling (https://www.infoq.com/news/2024/08/jspecify-java-nullability...) and language design (https://openjdk.org/jeps/8316779).

By @donatj - 2 months
The lack of default argument values initially annoyed me, but I kind of came to like it. It makes me put more thought into my function interfaces.

In the rare cases I do want a default it's usually reasonable to just add a second function that calls the first with the default value.

I don't end up doing this a lot, but I certainly have in a couple handful of cases. A lot of Go libraries for HTTP related activities do this with the default context. They'll have a function that accepts a context and a function that has the default context.

Example

https://github.com/slack-go/slack/blob/242df4614edb261e5f4f4...

Honestly, with good naming, I think this is just generally more readable and expectable behavior only takes three lines of code.

By @bediger4000 - 2 months
I've seen the python ordered dict thing bite ex-python programmers over and over, in two different ways.

First, assuming that the keys iterate in the order they're inserted, the cliche problem.

Second, marshalling JSON and unconsciously relying on the order in the JSON as hidden semantics. This makes it hard to understand the JSON as a human, as well as making what ought to be a portable format with other languages hard to reuse.

I've decided that Python is in the wrong here, not technically, but rather for encouraging humans to assume too much.

By @largbae - about 2 months
I wish that an unhandled error would crash its way up the stack automatically returning error if the next function up can do so, until it is either caught into a variable or can't be returned (panic if error can't be returned).

This would get close to python try/catch with even lighter syntax.

This would cut so much boilerplate hand carrying error up the stack.

By @MarkMarine - about 2 months
“I don't think the language needs to support the generic solution for nullability, that would be either having proper Union or Sum types.”

- then goes on to describe some of the problems sum types would solve. Why. Why doesn’t go need this? It was just presented as a blanket statement without a reason.

Personally, I see missing sum types as a major gap, and I reach for them all the time in other languages.

By @pansa2 - about 2 months
I've heard this several times - "Go would be great if only they added <my-favourite-feature>"...

Go's philosophy is that a coherent, curated feature set is as valid an approach to language design as the C++/Python/... approach of adding every possible language feature.

In particular I doubt Go will ever add null-safety - given the above philosophy, the language's pervasive use of "zero values", and its strong commitment to backwards compatibility.

By @wild_egg - 2 months
Been writing Go since 2012 and consider the status quo on all of these to be _features_. I may be in the minority there though
By @seabrookmx - 2 months
C# also solved the nullability problem after the fact. It integrates well with the "?." operator also found in Typescript.
By @moomin - 2 months
I am constantly criticising the C# community for not looking at other languages enough, but in this case it cuts both ways: C# has every feature mentioned here. Including a pretty good model for nullable.
By @jmyeet - 2 months
How is Go randomizing the map iteration order?

In Java, objects are responsible for their equals/hashCode implementations. The contract they must abide by is:

1. If two objects are equal, they must produce the same hash code; and

2. If they are not equal, they may produce the same hash code.

So if you had a list of 10 Strings and put them in a map in Java, it's likely you'll get a deterministic order for iterating over them unless you added a random factor. That factor could be a random seed tied to the map that you XOR the hash code with.

You can't really change the hash code itself to avoid a Hash DoS attack because you might break that contract. So how does Go (and Rust?) deal with that? Is Go adding a random seed to each hash map? If not, what is it doing?

As for nullability, there's no going back once you use a type system that expresses nullability.

Lastly, PHP arrays are incredibly convenient, ignoring the weirdness with them being array and hash map hybrids. But th ekey aspect is that they maintain insertion order when you use them like a map. This is so often what you want. Yes, other langauges do this too (eg Java's LinkedHashMap) but it's (IMHO) such a useful default.

By @karmakaze - 2 months
When I read 'unordered list', I was thinking it was more than 3 things:

  - Keyword and default arguments for functions
  - Nullability (or nillability)
  - Ordered maps in standard library
It's was a play on the hash map iteration.
By @daghamm - about 2 months
I too have a list of things I want to add to Go. For some reason, my list and OPs list are completely disjoint.

My wishlist is:

1. Add the ? operator is a short hand for if err != nil { return ..., err }

2. Allow member functions to have generic parameters (in addition to the struct parameters)

By @solraph - about 2 months
I agree with most of this list, and I'd add some kind of null coalescing or ternary operator, even if it was limited to one operater per expression, and a date time handling library that doesn't make me want to pull.my hair out.

There's several things that keep me on Go, single binary, decent built in tooling, and decent speed.

I've started playing around with tinygo on Pi Pico's, and after the dealing with getting C and C++ onto other MCUs it's a breath of fresh air.

But the rough edges are very rough. At some point another language is going to come along with better syntax with the same single binary and good tooling and I'll probably switch over as fast as possible.

By @materielle - 2 months
Interesting, I agree with the the main idea: in general I find Go a productive language, but there’s a few rough edges.

The specific examples don’t ring true to me though.

Instead of named arguments, use the functional optional pattern.

An ordered map can be implemented as a slice with a get function. If you want to stamp out some code duplication, Go has generics and iterators these days.

What I really miss are enums. Specifically, the ability to map two value sets to each other and get a compile time error if the mapping becomes stale due to a new value.

And other assorted stuff. Like better nil handling, the weird time and http client libraries, nil interfaces and slices, and so on.

By @BoingBoomTschak - 2 months
The reason for randomized hash is most probably to mitigate DOS attacks based on users knowing which hash function is used (Go is open source, after all) and finding a way to fill a map - any map - with untouched tokens.
By @nu11ptr - about 2 months
After getting used to Python's dicts being ordered since 3.6, I really wish every language would do this. I think it is natural to think about the keys being kept in inserted order, and it is very handy a lot of the time. Rust's `indexmap` for example is nearly as performant if I recall as the stdlib `HashMap`, so it probably wouldn't be much in the way of overhead to do so. Regardless, every major lang should have an ordered map in the stdlib in my opinion.
By @qaq - about 2 months
I think this perfectly illustrates the problem the thing I would most want to see is sum types e.g. we all have some particular thing we would want to add and we can't add em all.
By @aaomidi - about 2 months
I really dislike default values because it adds one more location where a value can be different from what id expect.

Honestly, be explicit rather than hoping for implicit behavior.

By @wwarner - about 2 months
I guess i don’t care very much about the “ergonomics”, like sorted hashes or whatever. I’d be really happy with faster execution, smaller memory footprint, smaller binaries. Conditional compilation would be great, but maybe smart utilization of code generation is sufficient here. For me the glaring gap i suffer with is data processing and ML, which is a community/library thing.
By @drdaeman - about 2 months
Nullability is poor man's `Maybe`/`Optional`, and I'd love to see that at the type system level (cf. `data Maybe a = Just a | Nothing`) rather than current {sql,null}.Null* mess or whatever was proposed in https://github.com/golang/go/issues/48702. But I doubt if that'll ever happen.
By @coin - about 2 months
Using Go, I miss enums
By @pcwelder - 2 months
I am a Python programmer with a bit of Golang experience. If it were upto me I'd absolutely do away with default function arguments in Python.

It is one of the rules now I live by. I don't have to worry about leaving an argument during calling anymore.

I think it fits well with the pythonic mantra of explicit is better than implicit.

By @gregjor - about 2 months
I haven’t found these specific things annoying.

PHP has all of the features listed in the article. Not saying anyone should choose PHP over Go.

By @everybodyknows - about 2 months
Am I the only one who feels the absence of a slice type strictly checked by both index and content?
By @malloc-0x90 - about 2 months
I am noob with Go, trying to learn it lately for more rapid development, and I'm coming from C.

I'm currently using LiteIDE, any good soul could suggest how what plugin to install in Zed, or Lapce, or Pulsar ?

(Unfortunately I refuse to use VS Codium because microsoft, and NeoVim because IBM CUA keybindings)

By @pancsta - about 2 months
I dont agree with even 1. Especially the yield example is very naive.
By @valyala - about 2 months
I'd like to remove generics and iterators funcs from Go, and stop adding programming language shit to Go. https://itnext.io/go-evolves-in-the-wrong-direction-7dfda8a1...
By @knodi - 2 months
> Nullability (or nillability)

> func(s *string) {

> // s maybe nil here, better check first

> }

If this happens, you don't have proper checks before this call. Clearly an error check was missed prior to this call.

By @tsimionescu - about 2 months
> Go isn't different, it has map, that is Go implementation of a hash table.

It's a bit nitpicky, but this always annoys me. Maps/dictionaries are not hashtables. A hashtable is one of the structures you can use to implement a map. A hashtable stores arbitrary items, not key-value associations, and can be used to quickly retrieve an item knowing its hash. If you want to implement a map using a hashtable, you need to also define a type that wraps a key-value pair and handles hashing and equality by only comparing the key.

Also, maps can be implemented with other underlying data structures as well. Java's standard library even offers a built-in TreeMap, which uses a red-black tree to store the pairs instead of HashMap's hashtable.