3

Currently I'm working on a data structure intended to store store key-value pairs uniquely and keep them sorted by key. Essentially it is a sorted dictionary and as such I am seeking to keep as much of the Collection and Dictionary semantics of Swift as I can.

In the documentation and Swift source (as best as I can find), Dictionaries have two subscripts. One is the most commonly used subscript by key (Github source):

extension Dictionary {
  ...
  public subscript(key: Key) -> Value? {
    @inline(__always)
    get {
      return _variantBuffer.maybeGet(key)
    }
    set(newValue) {
      if let x = newValue {
        // FIXME(performance): this loads and discards the old value.
        _variantBuffer.updateValue(x, forKey: key)
      }
      else {
        // FIXME(performance): this loads and discards the old value.
        removeValue(forKey: key)
      }
    }
  }
  ...
}

And the second is a subscript by position/index (Github) source) as part of its conformance to the Collection protocol:

extension Dictionary: Collection {
  ...
  public subscript(position: Index) -> Element {
    return _variantBuffer.assertingGet(position)
  }
  ...
}

When using these with a Dictionary keyed by something other than an Int, they behave exactly as expected because the subscripts are distinguished by different parameter types, ie: String vs Int.

let stringKeys = ["One": 1, "Two": 2, "Three": 3]
stringKeys["One"]   // 1
stringKeys[1]       // ("Two", 2)

When Ints are used as keys, the key subscript is used as desired.

let intKeys = [1: "One, 2: "Two, 3: "Three"]
intKeys[1]   // "One"

How does the Dictionary type accomplish this? It would seem to me that the Index and Key parameters of the subscripts are both Int and that the compiler should not know which was intended. Indeed, when I implement the same subscripts for my custom dictionary, the compiler gives exactly that error—"Ambiguous use of 'subscript'"—when I test it with Int keys.

At first I wondered if one was a default provided in a protocol extension and overridden by a more specific implementation, but from what I can tell above that is not the case. The only other theory I have is that Index is of some other type than 'Int` such that it remains unambiguous but I can't find my way to anything that confirms this. Can anyone shed some light on this? Beyond my immediate needs, it's quite a clever bit of behavior in Swift that I'd like to understand.

Thank you to all for reading and for your help!

Matthew M.
  • 31
  • 2

1 Answers1

2

The only other theory I have is that Index is of some other type than 'Int` such that it remains unambiguous but I can't find my way to anything that confirms this.

That's exactly how it is. Dictionary has two subscript methods:

public subscript(key: Dictionary.Key) -> Dictionary.Value?
public subscript(position: Dictionary<Key, Value>.Index) -> Dictionary.Element { get }

The first one takes a key and returns an (optional) value, the second one takes a Dictionary.Index (source code) and returns an (optional) Dictionary.Element, i.e. a key-value pair. Example:

let d : Dictionary = [1 : "one"]
if let idx = d.index(forKey: 1) {
    print(String(reflecting: type(of: idx))) // Swift.Dictionary<Swift.Int, Swift.String>.Index
    let kv = d[idx]
    print(String(reflecting: type(of: kv)))  // (key: Swift.Int, value: Swift.String)
}

Dictionary.Index is also used in other methods such as

public var startIndex: Dictionary<Key, Value>.Index { get }
public var endIndex: Dictionary<Key, Value>.Index { get }
public func index(where predicate: ((key: Key, value: Value)) throws -> Bool) rethrows -> Dictionary<Key, Value>.Index?

which are part of the Collection protocol.

Generally, the associated Index type of a Collection is not necessarily an Int, another example is String which has its own String.Index type.

Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382