Unexpected (to me) Array behavior

I wasn't expecting the "Index out of bounds" error on combo2 .. maybe I should have been. Apparently combo2 is 'inheriting' the indices of array1 and since indices 0 and 1 of array1 were not included in combo2, they don't exist there. Explicitly typing combo1 as [Int] gives me the behavior I expected (at, I suspect, the cost of copying the array).

let array1 = [0,1,2,3,4]
let array2 = [9,8,7,6,5]

let combo1: [Int] = array1[2...] + array2
let combo2 = array1[2...] + array2

var wowee1 = combo1[3]
wowee1 = combo1[2]   // = 4
wowee1 = combo1[1]
wowee1 = combo1[0]

var wowee2 = combo2[3]
wowee2 = combo2[2]   // = 2
wowee2 = combo2[1]      // Fatal error: Index out of bounds: file
                        // /AppleInternal/BuildRoot/Library/Caches/com.apple.xbs/Sources/
                        //  swiftlang/swiftlang-1103.8.25.8/swift/stdlib/public/core/SliceBuffer.swift, line 290

So combo2 isn't an Array, it's an ArraySlice, and those aren't always 0 based for their indexes. In this case combo2's first index is 2, so 0 is out of bounds.

3 Likes

This is correct. Expressions like array1[2...] create ArraySlices, not Arrays. You can read more about this behavior here (see Slices Maintain Indices.

1 Like

The other replies give the correct answer, but I just want to add one point. (Array) slices maintaining the same indexes they were created with is a feature. This way, an index that is valid in the slice not only is valid in the original, but it refers to the same element.

2 Likes

The others’ answers are correct. If you want to know the reasoning for it, or why : [Int] fixes it, see here:

(That post is about Data, but it applies to Array too.)


let combo2 = array1[2...] + array2

The fact that this even compiles seems like an unfortunate accident to me. It is very unclear what type it is supposed to produce. I’ve been working with Swift for years and even I had to run it through the compiler to find out that it results in an ArraySlice. (I expected some sort of FlattenSequence.) But this seems to be a butchering of what a slice is supposed to be. A slice of what? Printing it demonstrates that all the right elements are there. But the indices correspond neither to 0‐based logic, nor to the indices of any of its components. And slices hold onto the entire memory of their base collection. What memory is that abomination holding onto behind the scenes? Or allocating?

Even though + is available (presumably it was necessary for protocol conformance), I don’t recommend ever using it on an ArraySlice. Do something like the following instead, so that you always know very clearly what is happening with indices and memory under the hood:

let combo2 = Array(array1[2...]) + array2

"incredibly useful"

I live and learn .. many thanks.

Really, it's a feature? Because my expectation is that an expression like "array1[2...] + array2" is simply a collection of elements as if I had written, array1[2] + array1[3],,array1[n] + array2[0],,array2[n]. I don't expect metadata to come with it, I want a list of values. In my mind, this is confusion waiting to happen.

It is a collection of elements. The confusion comes because you think Collections are indexed from zero: that is, that collection[0] is the first element. In Swift, this is not true. The valid range of indices for a collection runs from startIndex to endIndex.

In fact, the only Collection that is guaranteed to have 0-as-start 1-as-step is Array. Data does come close, but falls short as it is its own subsequence.

Edit:

Now that I checked the Array doc, it actually doesn't say anything about the nature of startIndex and its step function :face_with_monocle:.

1 Like

In my mind, that's a calamity. The idiom of getting to the first element of an array with arrayname[0] is a 35 year old habit.

Wait, what? All we are talking about here is arrays. The examples are array literals and simple declarations like [Int].

It becomes a 35-year-old implementation burden though, as all kinds of collection needs to figure out how to make it works with that paradigm.

ArraySlice :wink:.

And it works fine. The above code isn’t getting the first element of an Array, it’s getting the first element of an ArraySlice. Different thing. If you want an Array, you can wrap whatever you have in Array().

With that said, I should clarify that I totally understand where you’re coming from. Lots of users have a trajectory into Swift where they enter the language, pick up Array as the first collection they use, and instantly subscript it. This appears to work, so they are not forced to understand Swift’s Collection model.

In many ways, the real mistake was to make Array‘s Index Int. This was done because Swift had no notion of offset-based indexing (and indeed still does not, though it’s in progress: see Offset Indexing and Slicing). If Array’s Index were opaque, it would force users to confront their understanding of what the system actually does.

And the system is defined sensibly. Index is not intended to be a convenient type for the user, it’s intended to be a convenient type for the Collection. The goal of Collection.Index is, roughly, if you have one you should be able to subscript Collection with it and get the element in O(1). For Array, an Int is fine. But for other Collections we use other types. Dictionary uses an opaque type that ends up holding an offset into the Collection. NIO’s CircularBuffer does a similar thing. You can construct weird Collections over things like the getaddrinfo list by making the Index type a pointer.

This solution is very flexible, but without offset-based indexing it bumps into the confusion boundary for many users very quickly. The Swift community really ought to push offset-based indexing over the line to help avoid these problems.

If an ArraySlice is returned from that expression, that's a magic expression. It breaks the principle of least astonishment.

1 Like

Ehh, that'd be a hard call. Given that Swift subscript rarely returns the same type. Admittedly it can look deceiving coming from other languages, but that's actually the most consistent as part of the language.

1 Like

I'd agree with the idea that if there is some amazing feature you get from having an opaque index, great. But don't call it array, and not expect people to be pissed off if it doesn't look like every other array they've dealt with.

I mean, again, Array behaves the way you want it to. ArraySlice does not.

1 Like

I don't think it's too hard a call, I am pretty damned astonished.

Sure.

I understand that, my problem is that I never would have expected what looks like a basic syntax to return a new type, and especially not a new type with a strange behavior. I can't think of another language which does this, why would anyone expect it?