Ben Stock Ben Stock - 2 months ago 25
Swift Question

Swift 3 Generic Extension Arguments

In Swift 2.x, I had a nice little setup that allowed me to store and retrieve dictionary values using enum members:

public enum UserDefaultsKey : String {
case mainWindowFrame
case selectedTabIndex
case recentSearches
}

extension Dictionary where Key : String {
public subscript(key: UserDefaultsKey) -> Value? {
get { return self[key.rawValue] }
set { self[key.rawValue] = newValue }
}
}


This allowed me to access values like this:

let dict = userDefaults.dictionaryForKey("SearchPrefs")
if let recentSearches = dict?[.recentSearches] as? [String] {
// Populate "Recent" menu items
}


… instead of having to access values like this:

let dict = userDefaults.dictionaryForKey("SearchPrefs")
if let recentSearches = dict?[UserDefaultsKey.recentSearches.rawValue] as? [String] {
// Populate "Recent" menu items
}


Note: The use of a string literal to access the dictionary from
NSUserDefaults
is for example purposes only. I wouldn't actually go out of my way to use an enum for dictionary keys, only to use a string literal to access the dictionary itself. :-)





Anyway, this has worked great for my needs, and it made reading and maintaining code involving
NSUserDefaults
a lot more pleasant.

Since migrating my project to Swift 3, however, I'm getting the following error:

extension Dictionary where Key: String {
public subscript(key: UserDefaultsKey) -> Value? { <---- Use of undeclared type 'Value'
~~~~~~
get {
return self[key.rawValue]
}
set {
self[key.rawValue] = newValue
}
}
}


I looked at the generated headers for
Dictionary
, and the generic
Key
and
Value
arguments are still present in the Generic Argument Clause of the
Dictionary
struct, so I'm not too sure what the issue is.

Do I need to rewrite the
where
clause to conform to some new Swift 3 grammar I'm unaware of? Or … can one no longer access generic placeholder types in extensions?

I just don't know what to do!

My project has only 28 migration errors left to resolve. I'm so close to actually getting to use Swift 3, so I'd love any pointers (as long as they're not
Unsafe
and/or
Raw
).

Thanks!

Answer

A generic parameter of a concrete type cannot be constrained to a concrete type, currently. This means that something like

extension Dictionary where Key == String

won't compile. It's a limitation of the generics system, and it hopefully won't be a problem in Swift 4.

There is a workaround though, but it's a bit hacky:

protocol StringConvertible {
    init(_ string: String)
}

extension String: StringConvertible {}

extension Dictionary where Key: StringConvertible {

    subscript(key: UserDefaultsKey) -> Value? {
        get { return self[Key(key.rawValue)] }
        set { self[Key(key.rawValue)] = newValue }
    }

}
Comments