Live data from Hacker News

a[low:high:max] in Golang – A Rare Slice Trick

build-your-own.org

51–60 of 131 posts

Re: a[low:high:max] in Golang – A Rare Slice Trick

#52

Question: what is the reason for the silent copy when append exceeds the original slice cap? It's a footgun avoided by reading the spec and (maybe) remembering it in practice, but it feels like it would be safer to throw a comp error and force the user to deal with it when a user is trying to exceed the cap of the underlying array? Alternative is defensively using len() and cap() for slice ops in which case error-ing…

Because you would not have any growable vector/list structure otherwise.

The real problem is that Go merrily lets you have copy-and-append operation on a slice (good), subviews of a slice so that you can share subsets of the data without copying it (good), at the same time (very bad: any operation on either will lead to confusion).

In most languages, subslicing gives you something of another type that can't be modified (or at least not accidentally). But in Go, if I call a function `do_smth_with_slice([]byte xs)`, there is no way for me to know whether this function expects `xs` to be mutable or not.

I'm sure that e.g. a C++ function taking an std::view can do some forbidden magic to still modify the underlying data, but at least the original intent is made clear by the argument type.

Re: a[low:high:max] in Golang – A Rare Slice Trick

#53
post #37

Earlier quoted context omitted.

If the slice syntax creates a copy, I would argue that it's simply syntactic sugar for array copying, not actually producing a slice (i.e. there is no slice type ). But yes, `somefunc(ary[1:])` in Python produces a copy, not a reference to the underlying value. You could build a more "true" slice class, but the builtin stuff doesn't do that. JavaScript is similar. Java however has `List .sublist` which largely behave…

It doesn’t look like any of those allow you to arbitrarily add and remove elements from the parent list in the same way Go does (if I understand Go slices correctly). Java does let you make modifications, but only under very tight restrictions: The semantics of the list returned by this method become undefined if the backing list (i.e., this list) is structurally modified in any way other than via the returned list.…

Go slices cannot change the structure (len or cap) of their "parent" slice. (Unlike Java, the semantics of every operation are even well-defined.) In some sense this is the root of the problem - the "child" slice can start using spare capacity it 'inherited' with no way to tell the parent it has done so, and the parent may inadvertantly pass some of its length as capacity down to its child.

Re: a[low:high:max] in Golang – A Rare Slice Trick

#54

Earlier quoted context omitted.

It doesn’t look like any of those allow you to arbitrarily add and remove elements from the parent list in the same way Go does (if I understand Go slices correctly). Java does let you make modifications, but only under very tight restrictions: The semantics of the list returned by this method become undefined if the backing list (i.e., this list) is structurally modified in any way other than via the returned list.…

Go slices cannot change the structure (len or cap) of their "parent" slice. (Unlike Java, the semantics of every operation are even well-defined.) In some sense this is the root of the problem - the "child" slice can start using spare capacity it 'inherited' with no way to tell the parent it has done so, and the parent may inadvertantly pass some of its length as capacity down to its child.

You can't insert or remove elements, but you can overrun the buffer??

I have a hard time thinking of a situation when that would be useful at all, let alone useful enough to be the default and widely-used behaviour! If I want to let the callee add stuff to the end of my list, can't I just pass them the whole list and let them modify that?

Re: a[low:high:max] in Golang – A Rare Slice Trick

#55

Earlier quoted context omitted.

Go slices cannot change the structure (len or cap) of their "parent" slice. (Unlike Java, the semantics of every operation are even well-defined.) In some sense this is the root of the problem - the "child" slice can start using spare capacity it 'inherited' with no way to tell the parent it has done so, and the parent may inadvertantly pass some of its length as capacity down to its child.

You can't insert or remove elements, but you can overrun the buffer?? I have a hard time thinking of a situation when that would be useful at all, let alone useful enough to be the default and widely-used behaviour! If I want to let the callee add stuff to the end of my list, can't I just pass them the whole list and let them modify that?

You cannot "overrun the buffer" nor can a callee add stuff to the end of your list; you can give them a memory buffer with unused space and they may use it, and then you can also mistakenly use it later.

Honestly, you seem to be too detached to understand this. If you really want to know how it works go through some official Go documentation. It's not fundamentally different than some feature in other languages, it's just that in Go this is the default growable list type and in other languages it's usually a non-default type.

As for whether it's ultimately useful - of course we can debate, but everyone still builds lists one element at a time and allocators still allocate by doubling, so there's at least one immediate and obvious use of capacity beyond the used length.

Re: a[low:high:max] in Golang – A Rare Slice Trick

#56

Earlier quoted context omitted.

Another example is typed arrays in JavaScript, which have both .slice() and .subarray(). One creates a copy, and one creates a new view into the same underlying memory.

You can’t change the length of a JS ArrayBuffer, or write past the end of your view.

ArrayBuffers have a byteLength and a maxByteLength and this is similar to Go's len and cap, with the `resize()` being equivalent to a reslice or append on the subview.

Re: a[low:high:max] in Golang – A Rare Slice Trick

#57
post #26

My fave golang slice trick is the len of an empty slice is 0, but the slice itself is == to nil, but the len of nil won't compile. Can't understand that one. https://go.dev/play/p/MslCkBphl7q?v=gotip

> the len of an empty slice is 0, but the slice itself is == to nil

That’s not true. An empty slice is initialized: []T{} or make(T[]); it’s not equal to nil.[1] The zero value nil slice is technically not an “empty slice”. Colloquially you may call a nil slice an empty slice, but the nil-ness is still an important distinction that manifests in e.g. encoding/json.Marshal; nil marshals to null, whereas an initialized empty slice marshals to [].

If you want to test the emptiness of a slice, test the length, don’t compare it to nil.

[1] https://go.dev/play/p/IP2NIgwvaTR?v=gotip

Re: a[low:high:max] in Golang – A Rare Slice Trick

#58

Earlier quoted context omitted.

You can't insert or remove elements, but you can overrun the buffer?? I have a hard time thinking of a situation when that would be useful at all, let alone useful enough to be the default and widely-used behaviour! If I want to let the callee add stuff to the end of my list, can't I just pass them the whole list and let them modify that?

You cannot "overrun the buffer" nor can a callee add stuff to the end of your list; you can give them a memory buffer with unused space and they may use it, and then you can also mistakenly use it later. Honestly, you seem to be too detached to understand this. If you really want to know how it works go through some official Go documentation. It's not fundamentally different than some feature in other languages, it's…

I coincidentally was just looking at some docs, and I think I have a handle on it (to it?:) now.

The specific strange design decision in Go is that you can create a child slice that has its own starting offset and length, but may or may not inherit its parent’s capacity. Just because it’s well-defined doesn’t mean it isn’t tricky.

Even if you kept the semantics the same but made cap=len for subslices by default, surely that would be an improvement. Rather than a “rare slice trick”, it ought to be normal. Or is there an advantage to the current default that I’m overlooking?

Re: a[low:high:max] in Golang – A Rare Slice Trick

#59
post #11
post #9

Earlier quoted context omitted.

But also strings are actually immutable sometimes (at the very least when hardcoded), and they can be converted to a `[]byte` slice that looks mutable, but panics when modified. AFAIK no other slice-like data in Go behaves like this. Fun!

This is a pretty good vignette of how Go is designed. "Users don't need this feature (immutability, polymorphism, etc.), let's not include it. Ah crap, turns out we actually need it for a core language feature. Is there a lesson we can take away here? Nah, just make an ad-hoc implementation of the functionality in this one place."

I don’t think you can fairly call go’s generics an “ad-hoc” implementation. They added syntax for it.

Re: a[low:high:max] in Golang – A Rare Slice Trick

#60

Earlier quoted context omitted.

You can’t change the length of a JS ArrayBuffer, or write past the end of your view.

ArrayBuffers have a byteLength and a maxByteLength and this is similar to Go's len and cap, with the `resize()` being equivalent to a reslice or append on the subview.

The difference is that those operations are on the root ArrayBuffer in JS, not the TypedArray views on that buffer.

In Go, the underlying array is fixed, and each slice independently has resize/reallocate operations.

The JS approach seems better and more comprehensible to me. (I note also that MDN lists “resizable” and “maxByteLength” as new experimental features.)

Post reply on HN