Slice: a leaky abstraction in Go?

First, an important announcement: as you might know, ~ one year ago I’ve started Go vs C# series. Unfortunately, I didn’t have a chance to write anything in addition to the first post there (i.e. goroutine & async/await comparison). But recently I’ve got some time to continue the series, so here is my near-term plan on this:

  • Compare Go and .NET garbage collection. The code for this test (GCBurn) is ~ 90% ready, so I’d be happy if someone who knows Go really well could review it or contribute before I share the findings. Please message me here (or better on Facebook) if you’re willing to help.
  • Share an update on async-await vs goroutines. There were a few really valuable comments + later I updated my .NET tests + .NET Core 2.1 was released — in short, I underestimated async-await performance there. So I want to provide more details on that, + talk on a few other interesting aspects related to async programming in both languages.
  • Continue the series — likely, the next most logical step would be to talk about Go & .NET type system.

Now, getting back to the subject: I’ve got a chance to learn Go a bit better while working on GCBurn, so here is one thing that I’ve discovered: seemingly, slice in Go an extremely leaky abstraction. Check out this code:

What bothers me is:

  • If you look at the code, append(slice, item) looks like pure function, though in reality, it’s not
  • Somewhat similar is intact for slice[:index]: yes, it is a pure function, but unfortunately what it returns — in conjunction with append — allows you to mutate the original slice.
  • All of this means that you need to be very careful w/ [:index] and append calls on slices you get (e.g. as arguments / struct fields) — you generally can’t assume you can use these operations on them w/o copying the slices.

If you use Go for a while, I’d love to hear some comments on this — mostly, how it’s justified in Go philosophy?

Creator of , ex-CTO @