Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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
like image 647
Rob N Avatar asked Nov 02 '16 19:11

Rob N


People also ask

What is associated type in protocol Swift?

What is an associated type? An associated type can be seen as a replacement of a specific type within a protocol definition. In other words: it's a placeholder name of a type to use until the protocol is adopted and the exact type is specified. This is best explained by a simple code example.

What is type erasure in Swift?

Type-erasure simply means "erasing" a specific type to a more abstract type in order to do something with the abstract type (like having an array of that abstract type). And this happens in Swift all the time, pretty much whenever you see the word "Any."

Can only be used as a generic constraint because it has self or associated type requirements?

Protocol 'SomeProtocol' can only be used as a generic constraint because it has Self or associated type requirements. Code that uses a protocol that relies on associated types pays the price. Such code must be written using generic types. Generic types are also placeholders.

What is self requirement?

A Self requirement is a placeholder in a protocol for a type, used as part of the protocol, which is replaced by the concrete type that conforms to the protocol. A Self requirement can thus be thought of as a special case of an associated type.


2 Answers

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!")
}
like image 92
Hamish Avatar answered Oct 22 '22 15:10

Hamish


[Edited to fix: : SpecialValue, not = SpecialValue]

This is not possible. SpecialValueController is an "incomplete type" conceptually so the compiler cannot know. SpecialValueType, although it is constrained by SpecialValue, it is not known until it is determined by any adopting class. So it is a really placeholder with inadequate information. as?-ness cannot be checked.

You could have a base class that adopts SpecialController with a concrete type for SpecialValueController, and have multiple child classes that inherit from the adopting class, if you're still seeking a degree of polymorphism.

like image 1
BaseZen Avatar answered Oct 22 '22 14:10

BaseZen