Skip to content(if available)orjump to list(if available)

Go Data Structures: Interfaces (2009)

Go Data Structures: Interfaces (2009)

71 comments

·February 5, 2025

meander_water

The comparisons to python are a bit dated now. All of the examples can be implemented in python using typing extensions [0] and statically checked before runtime using mypy [1].

[0] https://docs.python.org/3/library/typing.html (from Python 3.5 - released Sep 2015)

[1] https://github.com/python/mypy (v0.1 released Sep 2009)

rednafi

One thing I like about Go is that you can read a decade-old blog and still find it somewhat relevant.

Python was the first language I made money with. However, these days, I struggle to read and make sense of type-ridden, generic-filled, Pydantic-infested Python code.

rollcat

> Python was the first language I made money with. However, these days, I struggle to read and make sense of type-ridden, generic-filled, Pydantic-infested Python code.

I'm in the same boat. Python was great when it was a snake. I liked additions like the "with" statement - they were very pythonic.

I think it's good when the language evolves, although the direction Python took feels more like grafting - oh people like cats, so let's graft some fur onto the snake; people like bats so let's attach wings. The creature no longer resembles a snake, or any other animal for that matter.

I still think Python 3.0 was the right thing to do - get rid of "old style" classes, default to Unicode strings, be strict about mixing Unicode with bytes, etc. While there, I wish we got rid of the __init__(self, ...) crap - repeating yourself three times was absurd. The language was getting better up until 3.4 or so, and it slowly started going downhill from there, async being the inflection point.

I don't think you can fix it anymore. Python 4.0 will never happen, at least not the way 3.0 did.

Go has its problems too. "if err != nil" is awful - stack unwinding exists, but it's awkward and "bad style". Tuples exist, but not as a first-class object. Generics dropped way too late (but at least I'm happy we went thru so many proposals, finally settling on something actually reasonable). Past mistakes cannot be easily undone, so I'm happy it's taking a more conservative approach.

dfawcus

Re tuples, I guess you mean the multiple return values, and this assignment form: https://go.dev/play/p/vxm6lV7JS4z

However how would real first class tuples be an improvement in Go? Alef had them, and allowed various manipulations, as well as returning them, and passing them to functions.

I note that they are present in Hare, but not present in Odin. Where the latter has the Go inspired multiple return values, but (AFAICS) no tuples, but does add tagged unions.

Generally I'd not want to store a tuple, preferring a struct with named fields.

So the only uses I can think of are those temporary ones for multiple return values and assignments, which are already covered.

throawayh666

Wow, we're on pretty much the same path.

Loved Python when I got into it circa 2011. Didnt have prior programming experience, minus basic BASIC and HTML. It was simple enough and the stdlib include enough things to get me going, but it had enough complexity to intrigue me to dive deeper (list comprehensions, bytes vs strings, inheritance vs composition) and I think I learned a ton about programming thanks to it.

But these days when I see modern Python, it looks "uncomfortable". It has so many features, and so many ways of doing things that just figuring that out feels like a massive time sink.

I write server-side software with Go now. I feel like with Go I can just sit down and start solving problems. That applies even to sitting down and diving into a 10 year old codebase.

dotancohen

Please state you musings about the async. I'm just getting into that right now and I wonder if my misgivings are a result of my age or a fundamental issue with the Python implementation of the concept.

Seriously, all and any insight and advice appreciated. Thanks.

rednafi

This is a more elaborate version of how I feel about Python these days. Python is still a much nicer language than something like JS, but it hasn’t been very successful at saying no to things.

I like type hints, but it’s easy to go overboard with them. Pydantic and FastAPI are great. The problem is that typenauts and academics coming from other languages are trying to bring every feature under the sun to Python. The core team hasn’t been able to fight this barrage of feature requests.

The same is true for Go. I regularly see Rust/Haskell folks talking about how things could be better if Go had xyz feature. While it’s true that Go would probably have benefited from a little more expressiveness, how much more? Where do you stop?

I like Go because it’s not Rust or Zig. I mostly write server software, and Go is far more productive in that space. The Go team understands this and is much more protective about scope creep. Keep your type theory off my lawn and let me make money in peace, please.

globular-toast

More specifically, you can do interfaces with Protocol[0]. The main problem with it seems to be as soon as you a single Protocol mypy becomes extremely slow.

[0] https://docs.python.org/3/library/typing.html#typing.Protoco...

zknill

A quirk of Go is that I can cast a `[]string` to `interface{}`, but I cannot cast `[]string` to `[]interface{}`. This blog post is my go-to explanation for why the second is not possible but the former is.

duskwuff

> I can cast a `[]string` to `interface{}`, but I cannot cast `[]string` to `[]interface{}`

Surely that's just contravariance, though? You can't cast []string to []any because that would allow you to write non-strings to it.

saghm

Yeah, I have plenty of criticisms of Go, but this is something that it got right.

For those who aren't familiar with the issue, in Java you can assign an array of a subclass to a variable declared as an array of the superclass, which leads to issues if you actually try to mutate it. Imagine if Cat and Dog both inherit from Animal, assigning a Dog[] to an Animal[] is totally valid, but then setting one of the elements to a Cat will throw an an exception.

nlitened

I can’t help but keep noticing that all of the notorious problems with OOP are not due to inheritance, but due to mutability.

saurik

Is that really a "quirk" of specifically Go? I am pretty sure Java--the implementation of which defined a lot of how numerous languages handle this kind of thing--has the same behavior: you can cast String[] to Object, but not Object[].

kgeist

C# allows casting string[] to object[], but this requires the compiler to insert runtime type checks each time the array elements are modified:

  object[] array = new string[10];  
  // throws a run-time exception 
  array[0] = 10;

skitter

C# inherited this from Java, which added it because it was designed without generics and therefore decided that the additional expressiveness provided by enforced array covariance was worth the tradeoff of having an unsound type system.

neonsunset

Note that array covariance is largely considered a design mistake and if .NET was to be redone it wouldn't have one (and a bunch of smaller things that are an artifact of generics getting introduced in C# 2.0 and not 1.0, well, the problem is much worse in Go heh).

Other data structures like List<T>, Span<T>, etc. do away with covariance. There's an upcast for ReadOnlySpan<T> but only because it's zero-cost and does not introduce the issues stemming from covariance.

Luckily, you almost never see someone use array covariance beyond occasional object[] upcasts (and the compiler is also good at reasoning whether to insert covariance checks or not).

sltkr

Actually in Java you can cast String[] to Object[] although this is technically unsound.

This how generic methods like Arrays.sort() are implemented.

saurik

Huh, fair enough!! What's then funny in the context of this thread is that, while I am sure I knew that 20-25 years ago, it feels so wrong that it would support that -- and more like a "quirk" that it ever would -- that it surprises me (though it certainly doesn't shock me: most things have many quirks).

philippta

Because the latter would incur a hidden O(n) computation.

rollcat

It's not just the O(n), you must think of interface{} as a pair (concreteType, pointerToMutableData). It's a can of worms, allocating n*2 word-sized objects for each rune is just the surface layer - strings in particular are immutable.

I think Go does the right thing to make this allocation and assignment explicit, you may be a little less surprised with how the program actually behaves. https://go.dev/play/p/PzuBpM66VX2

null

[deleted]

TheDong

since when has that stopped go? “[]rune(someString)” is O(n) and quite inobvious

The O(n) loop is here: https://github.com/golang/go/blob/215de81513286c010951624243...

foldr

It’s obvious that this is O(n), no? You’re casting something immutable to something mutable, so it has to be copied.

rednafi

I didn’t know even know this. TIL, thanks.

iTokio

If you are mindful of performance, one thing to be careful of, is that interfaces often prevent inlining and can cause performance issues.

Generally speaking the Go compiler seems to have trouble with inlining, PGO (profile guided optimization) can help though.

tapirl

Storing concrete values into interfaces often cause allocations, which has a larger impact on performance than inlining.

nasretdinov

Yeah I was really surprised by how much of a performance difference it made when calling variadic functions that support `args ...interface{}`. Literally every since call is going to be an allocation of multiple parameters when passing structs without a pointer

indogwetrust

> Go compiler seems to have trouble with inlining

not "have trouble", a deliberate design to keep compile times fast and (emitted) code size reasonable

the_gipsy

Ah yes, whichever it is, always design.

neonsunset

You have to manually collect and then apply the profile though (very few teams do this). A proper solution is doing a whole program view optimization (at least like the one done by .NET's NativeAOT). But typically of Go design philosophy, a stop-gap solution was chosen instead.

null

[deleted]

andreyvit

@dang Probably needs (2009)?

null

[deleted]