Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In Swift, what are the semantics of subscripts yielding (or receiving) non-object types?

Tags:

swift

The Swift book does not clearly document the semantics of non-object types in conjunction with subscripts. The most obvious example is a Dictionary whose value type is an Array: Array is a struct. Therefore, when a subscript returns it, we should expect it to be copied. This, however, is very inconvenient. Fortunately, it seems also not to be the case—at least not in Xcode 8.2.1 (Swift 3.1?).

Consider these examples:

var a: [Int] = [0]                          // [0]
var b = a                                   // [0]
b.append(1)
b                                           // [0, 1]
a                                           // [0]

As we expect, the array a is copied when it is assigned to b. In contrast,

var h: [Int: [Int]] = [0: [0]]              // [0: [0]]
h[0]!.append(1)
h[0]                                        // [0, 1]

If the subscript were simply returning the value using ordinary semantics, we would expect that h[0] would still equal [0] after h[0]!.append(1), but in fact it is the array in the dictionary that is appended to. We can even see if we like that the value remains at the same location in memory, suggesting that it is semantically the same array that was appended to. (Being at the same location does not imply that, but it is not in conflict with that, either.)

var h: [Int: [Int]] = [0: [0]]              // [0: [0]]
var aptr: UnsafePointer<[Int]>? = nil
withUnsafePointer(to: &h[0]!) { aptr = $0 }
aptr                                        // UnsafePointer(0x7FFF5351C2C0)
h[0]!.append(1)
withUnsafePointer(to: &h[0]!) { aptr = $0 }
aptr                                        // UnsafePointer(0x7FFF5351C2C0)

This fortunate but seemingly-undocumented behavior does not apply only to Arrays as Dictionary values.

struct S: CustomStringConvertible {
    var i: Int = 0
    var description: String { return "S(i=\(self.i))" }
}

var g: [Int: S] = [0: S()]
g[0]!                                       // S(i=0)
g[0]!.i = 5
g[0]!                                       // S(i=5)

And, in fact, it does not even apply only to the subscripts of Dictionary types.

struct T {
    var s: S? = S()
    subscript(x: Int) -> S? {
        get {
            return self.s
        }
        set(s) {
            self.s = s
        }
    }
}
var t = T()
t[0]!                                       // S(i=0)
var tptr: UnsafePointer<T>? = nil
withUnsafePointer(to: &t) { tptr = $0 }
tptr                                        // UnsafePointer(0x1007F6DB8)
t[0]!.i = 5
t[0]!                                       // S(i=5)
withUnsafePointer(to: &t) { tptr = $0 }
tptr                                        // UnsafePointer(0x1007F6DB8)

Notably, this does somehow involve the setter: If the set under subscript is removed from the definition of T, the statement t[0]!.i = 5 produces the error

error: cannot assign to property: subscript is get-only

My preferred answer would be a pointer to some Swift documentation that clearly explains the semantics of modifications to non-object values obtained through subscripts. It appears that the language behaves as I would like it to, but I'm not comfortable relying on this behavior while it seems inconsistent with the documentation.

like image 689
Jamborino Avatar asked Dec 20 '25 11:12

Jamborino


1 Answers

Array is implemented using copy-on-write, so it is not in fact copied each time it is assigned but only when it needs to be as determined by its internal state. It seems this behaviour is implemented in such a way that it is not triggered after being returned from a subscript.

update

Array subscripts are implemented using addressors, essentially the subscript accesses the array element directly. You can read some details in this document: https://github.com/apple/swift/blob/master/docs/proposals/Accessors.rst

Particularly note the section Mixed addressors, quote: 'Mixed addressors have now been adopted by Array to great success', perhaps dictionaries are using the same system now.

Also see this tweet: https://mobile.twitter.com/slava_pestov/status/778488750514970624 by someone who apparently works on the swift compiler at Apple, the related thread has some interesting links.

But essentially the answer to your question is that this isn't documented in a user friendly way, it is using special optimisations behind the scenes to make sure it works efficiently. I agree that a detailed explanation in the documentation would be helpful!

If you are concerned at using undocumented behaviour in your code a workaround would be to wrap your value type in a class before storing it in an array or dictionary. But it seems unlikely that the Swift team will make a change that breaks existing code in this way.

like image 169
simonWasHere Avatar answered Dec 24 '25 06:12

simonWasHere



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!