Subscript Setter Holding a Strong Reference?

I am wondering if someone can explain the behavior I'm seeing in this code:

class Node {
    let value: Int
    var children: [Int : Node]
    
    init(_ value: Int) {
        self.value = value
        self.children = [:]
    }
    
    subscript(_ index: Int) -> Node? {
        get { children[index] }
        set { children[index] = newValue }
    }
}

var node1 = Node(1)
node1[2] = Node(2)
print(isKnownUniquelyReferenced(&node1)) // true
print(isKnownUniquelyReferenced(&node1[2])) // false

var node2 = Node(1)
node2.children[2] = Node(2)
print(isKnownUniquelyReferenced(&node2)) // true
print(isKnownUniquelyReferenced(&node2.children[2])) // true

It seems like the subscript setter is maintain a strong reference to the instance of
Node.

I’m not an expert here, but I notice two things.

First, it does not matter how you set the values. In your example, whether you write node[2] = Node(2) or node.children[2] = Node(2), the behavior is the same. All that matters is what you pass to isKnownUniquelyReferenced. In both cases we have:

isKnownUniquelyReferenced(&node[2])          // false
isKnownUniquelyReferenced(&node.children[2]) // true

Next, it is indeed the subscript setter which results in the behavior difference. Replacing the set block with a _modify block makes both calls to isKnownUniquelyReferenced return true. The get block may be left as-is, or replaced with a _read block. I’m not certain what difference it makes:

subscript(_ index: Int) -> Node? {
  _read { yield children[index] }
  _modify { yield &children[index] }
}

Of course, both _read and _modify are undocumented and not officially part of the language. They are, however, used extensively in the standard library.

1 Like

Thank you, this led me down a new path, and I found this:

https://github.com/apple/swift/blob/main/docs/OwnershipManifesto.md#generalized-accessors

I don’t know whether these accessors are part of the language, but I’m going to play with it when I return to my desk.

i have been using _read and _modify (_modify more than _read) in production for years and i know many other swift developers who do the same. to me, they are part of the language already, as it’s very difficult to write performant Swift libraries without _modify.

i don’t know why they haven’t been naturalized yet, they are so widely used that changing the semantics would break a ton of libraries. the situation is a lot like @_inlinable before it lost the underscore.