Reputation: 2018
Consider the following code:
protocol JSONParserType {
associatedtype Element
}
// MARK: - Entities
struct Item {}
// MARK: - Parsers
struct OuterParser<T: JSONParserType where T.Element == Item>: JSONParserType {
typealias Element = Item
let innerParser: T
init(innerParser: T = InnerParser()) {
self.innerParser = innerParser
}
}
struct InnerParser: JSONParserType {
typealias Element = Item
}
The OuterParser
has a child parser that should be constrained to a specific type. Unfortunately providing a default value in the initializer (or in the property definition itself) does lead to the compiler throwing a "Default argument value of type 'InnerParser' cannot be converted to type 'T'".
If I remove the default value assignment and just instantiate the OuterParser
providing the InnerParser
explicitly, everything is fine.
let outerParser = OuterParser(innerParser: InnerParser())
My question is what's the reason that the approach providing a default value that actually meets the constraints does not work.
Upvotes: 4
Views: 757
Reputation: 80781
The problem is that the actual type of T
isn't defined by the class – it's defined by the code that uses the class. It will therefore be defined before you do anything in your class (at either instance or static level). You therefore can't assign InnerParser
to T
, as T
has already been defined to be a given type by that point, which may well not be InnerParser
.
For example, let's consider that you have another parser struct:
struct AnotherParser: JSONParserType {
typealias Element = Item
}
and let's assume that your current code compiles. Now consider what would happen when you do this:
let parser = OuterParser<AnotherParser>()
You've defined the generic type to be AnotherParser
– but the initialiser will try to assign InnerParser
to your property (now of type AnotherParser
). These types don't match, therefore it cannot possibly work.
Following the same logic, this implementation also won't work:
struct OuterParser<T: JSONParserType where T.Element == Item>: JSONParserType {
typealias Element = Item
let innerParser: T
init() {
self.innerParser = InnerParser()
}
init(innerParser: T) {
self.innerParser = innerParser
}
}
As there's no guarantee that the generic type T
will be the same type as InnerParser
. Sure, you can force downcast to T
– but that'll just make you code crash if the types aren't compatible.
Unfortunately, there's no real clean solution to this problem. I think the best your best option is probably to create two factory methods for creating your OuterParser
instance.
enum Parser {
static func createParser() -> OuterParser<InnerParser> {
return OuterParser(innerParser:InnerParser())
}
static func createParser<T>(innerParser:T) -> OuterParser<T> {
return OuterParser(innerParser:innerParser)
}
}
let innerParser = Parser.createParser() // OuterParser<InnerParser>
let anotherParser = Parser.createParser(AnotherParser()) // OuterParser<AnotherParser>
We're using an caseless enum here to avoid polluting the global namespace with extra functions.
Although this isn't very Swifty, and for that reason I would also recommend maybe rethinking your logic for how you define your parsers.
Upvotes: 1
Reputation: 1760
type T
more like a child protocol
of JSONParserType
you can convert it:
init(innerParser: T = InnerParser() as! T) {
self.innerParser = innerParser
}
Upvotes: 0