News for hackers. Stuff that matters.
Re:a[low:high:max] in Golang – A Rare Slice Trick by DougBTX on Saturday March 18, 2023 @03:29PM (#35212282)
So in other words, appending to a slice may overwrite data in the middle of a different slice?
Re:a[low:high:max] in Golang – A Rare Slice Trick by Groxx on Saturday March 18, 2023 @03:31PM (#35212296)
Yep. Depending on how the slice was constructed, not how it is used.

Slices (largely) behave like this in most languages, Go's contribution is mostly that slices and append are ubiquitous, so pretty much every Go coder is exposed to it. Prior to generics, literally any alternative was so much more work they essentially haven't been used.

Re:a[low:high:max] in Golang – A Rare Slice Trick by iainmerrick on Saturday March 18, 2023 @04:15PM (#35212695)
Slices (largely) behave like this in most languages

Is that really the case? I can’t think of many languages that let you construct a view into the middle of array, pass that view as an argument to a function call, and allow that function to add new values into your array via the view.

In Python or JS, for example, the “slice” would just be a brand new array, right?

Re:a[low:high:max] in Golang – A Rare Slice Trick by Groxx on Saturday March 18, 2023 @04:53PM (#35212994)
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 behaves like Go: https://docs.oracle.com/javase/8/docs/api/java/util/List.htm... . Sometimes these kinds of things are also referred to as "views". Go calls them slices though, and this is a Go article.

edit: C# apparently has "spans": https://learn.microsoft.com/en-us/archive/msdn-magazine/2018...

Re:a[low:high:max] in Golang – A Rare Slice Trick by klodolph on Saturday March 18, 2023 @05:12PM (#35213156)
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.
Re:a[low:high:max] in Golang – A Rare Slice Trick by iainmerrick on Saturday March 18, 2023 @06:36PM (#35213860)
You can’t change the length of a JS ArrayBuffer.
Re:a[low:high:max] in Golang – A Rare Slice Trick by morelisp on Saturday March 18, 2023 @07:48PM (#35214410)
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 by iainmerrick on Saturday March 18, 2023 @08:24PM (#35214633)
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.)

Re:a[low:high:max] in Golang – A Rare Slice Trick by iainmerrick on Saturday March 18, 2023 @06:34PM (#35213846)
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. (Structural modifications are those that change the size of this list, or otherwise perturb it in such a fashion that iterations in progress may yield incorrect results.)

I take that to mean the parent list can’t be modified at the same time, nor can multiple views be modified.

So, I think Go’s ability to do those structural modifications on slices is rather unusual (as well as error-prone and not obviously useful).

Re:a[low:high:max] in Golang – A Rare Slice Trick by morelisp on Saturday March 18, 2023 @07:19PM (#35214216)
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 by iainmerrick on Saturday March 18, 2023 @07:31PM (#35214304)
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 by morelisp on Saturday March 18, 2023 @07:41PM (#35214361)
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.

Re:a[low:high:max] in Golang – A Rare Slice Trick by iainmerrick on Saturday March 18, 2023 @08:06PM (#35214523)
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 by Groxx on Saturday March 18, 2023 @10:12PM (#35215304)
cap==len is effectively the default - most slicing is done with [start:end], not [start:end:additional-cap]
Re:a[low:high:max] in Golang – A Rare Slice Trick by iainmerrick on Saturday March 18, 2023 @11:28PM (#35215737)
No it’s not -- I just checked the language specification and it says:

After slicing the array a

a := [5]int{1, 2, 3, 4, 5}

s := a[1:4]

the slice s has type []int, length 3, capacity 4

Many of us here are arguing that the default should be cap==len==3, but it’s not, it’s 4.

Re:a[low:high:max] in Golang – A Rare Slice Trick by Groxx on Monday March 20, 2023 @12:08AM (#35227172)
Welp. TIL.

Yeah, that's much more error prone I think. It's the sort of thing that only kinda makes sense on zero-valued arrays... and even then it's dubious at best.

Bleh. I'm gonna go review some old code now D:

Re:a[low:high:max] in Golang – A Rare Slice Trick by morelisp on Sunday March 19, 2023 @06:08AM (#35217560)
I mean, if you want Java, it's right there.

The advantage is that you get the fast path by default. Since Go programs are generally not awash in buffer reuse bugs despite its semantic trickiness, this seems like something different languages can reasonably prioritize differently.

Re:a[low:high:max] in Golang – A Rare Slice Trick by masklinn on Saturday March 18, 2023 @04:25PM (#35212778)
> Slices (largely) behave like this in most languages

Only on assignment, not in appending.

> Go's contribution is mostly that slices and append are ubiquitous

Go's contribution is the conflation of slices and vectors, which in most languages are separate (or really most languages only have the latter and don't provide access to backing arrays, thus precluding this specific confusion).

Re:a[low:high:max] in Golang – A Rare Slice Trick by klodolph on Saturday March 18, 2023 @05:21PM (#35213229)
In C++, until C++20’s std::span, you would just use std::vector for stuff you can modify and const std::vector for stuff you can’t.

I think the real problem is that Go’s type system doesn’t catch the common error of keeping a reference to something you don’t own, or similar errors. C# catches some of these errors by letting you return a IReadOnlyList<T> or some other restricted type. Golang has ways to narrow certain types, but the slice type is primitive and has no narrowed read-only variation. C# instead forces you to pay a higher runtime cost, because you’re paying a lot more for indirection with IReadOnlyList<T>.

People fret about the difference between T[] and List<T> in C#, and they do come with different performance characteristics… Go’s choice to use slices everywhere does have a certain advantage that you’re getting the fast path everywhere, and you’re spending less time thinking about which one to use.

Not trying to say that Go is “right” here, it’s just my viewpoint that these language design decisions are rational.

Re:a[low:high:max] in Golang – A Rare Slice Trick by or10nx on Sunday March 19, 2023 @02:24AM (#35216534)
> People fret about the difference between T[] and List<T> in C#

That doesn't align with my experience. I've worker for small (4 programmers) and large (100s of programmers) C# shops, and I don't recall people "fretting" about T[] and List<T>. People see T[] as a non-growable, less useful version of List<T>. HashSet<T> vs List<T> seems to cause much more trouble for novice (and sometimes experienced) C# programmers.

Re:a[low:high:max] in Golang – A Rare Slice Trick by klodolph on Sunday March 19, 2023 @11:16PM (#35226828)
I'm thinking more about the places where people care about performance, since accessing a T[] is faster than accessing a List<T> (less indirection).