swiftgenericsswift3protocolsassociated-types

In Swift, how to cast to protocol with associated type?


In the following code, I want to test if x is a SpecialController. If it is, I want to get the currentValue as a SpecialValue. How do you do this? If not with a cast, then some other technique.

The last line there won't compile. There error is: Protocol "SpecialController" can only be used as a generic constraint because it has Self or associated type requirements.

protocol SpecialController {
    associatedtype SpecialValueType : SpecialValue
    var currentValue: SpecialValueType? { get }
}
...
var x: AnyObject = ...
if let sc = x as? SpecialController {  // does not compile

Solution

  • Unfortunately, Swift doesn't currently support the use of protocols with associated types as actual types. This however is technically possible for the compiler to do; and it may well be implemented in a future version of the language.

    A simple solution in your case is to define a 'shadow protocol' that SpecialController derives from, and allows you to access currentValue through a protocol requirement that type erases it:

    // This assumes SpecialValue doesn't have associated types – if it does, you can
    // repeat the same logic by adding TypeErasedSpecialValue, and then using that.
    protocol SpecialValue {
      // ...
    }
    
    protocol TypeErasedSpecialController {
      var typeErasedCurrentValue: SpecialValue? { get }
    }
    
    protocol SpecialController : TypeErasedSpecialController {
      associatedtype SpecialValueType : SpecialValue
      var currentValue: SpecialValueType? { get }
    }
    
    extension SpecialController {
      var typeErasedCurrentValue: SpecialValue? { return currentValue }
    }
    
    extension String : SpecialValue {}
    
    struct S : SpecialController {
      var currentValue: String?
    }
    
    var x: Any = S(currentValue: "Hello World!")
    if let sc = x as? TypeErasedSpecialController {
      print(sc.typeErasedCurrentValue as Any) // Optional("Hello World!")
    }