Dictionary init(tuples, uniquingKeysWith:) method forces me to handle throw, while merge doesn't?

Is this a Swift4.1 bug, or am I doing something wrong?

public extension Dictionary {
    public init<ST : Sequence>(overwritingWithLatest tuples: ST) where ST.Iterator.Element == (Key, Value)  {
           self.init(tuples, uniquingKeysWith: { $1 })
    }
}

yields a compiler error that the call to self.init() can throw, and I didn't handle it. Yet

public extension Dictionary {
    public init<ST : Sequence>(overwritingWithLatest tuples: ST) where ST.Iterator.Element == (Key, Value)  {
          self.init()
          self.merge(tuples, uniquingKeysWith: { $1 })
    }
}

is accepted. The documentation shows that both the init() I called and the merge() have identical signatures with respect to rethrows.

Bug, or user error?

Seems like a bug.

1 Like

On a tangential note, the Sequence protocol defines it's own Element associated type that's required to be the same type as Iterator.Element, so you shouldn't have to type ST.Iterator.Element anymore, ST.Element will do the exact same thing.

If you file a bug report, it might be interesting to note that:

public extension Dictionary {
    public init<ST : Sequence>(overwritingWithLatest tuples: ST) where ST.Iterator.Element == (Key, Value)  {

        self = .init(tuples, uniquingKeysWith: { $1 }) // <-- This compiles
        
        // even though your formulation (which afaics is equivalent) doesn't:
        //self.init(tuples, uniquingKeysWith: { $1 }) // ERR: Call can throw …
    }
}

Or, more generally:

struct S {
    init(_ fn: (Int) throws -> Int) rethrows { }

    init() {
        // The following line results in an invalid compile time error:
        self.init { $0 } // ERROR: Call can throw, but it is not marked
                         //        with 'try' and the error is not handled
        
        // Workaround: This equivalent code compiles:
        // self = .init { $0 } // OK, no error
    }
}
1 Like