I'm trying to define a protocol that requires an enum
with raw value String
to be implemented.
I don't believe that it's currently possible to enforce the use of enum
, and I'm not sure I really care as long as somewhere down the line I can call fromRaw()
and receive a String
.
So, I'm trying to maintain the brevity of the following while restricting Beta
to be an enum
where the raw value is a String
:
protocol Alpha {
typealias Beta: RawRepresentable
}
struct Gamma: Alpha {
enum Beta: String {
case Delta = "delta"
}
}
struct Eta<T: Alpha, U: RawRepresentable where T.Beta == U> {
let alpha: T
let beta: U
init(alpha: T, beta: U) {
self.alpha = alpha
self.beta = beta
println("beta is: \(beta.toRaw())")
}
}
let gamma = Gamma()
Eta(alpha: gamma, beta: .Delta) // "beta is delta"
The problem with the above is that other raw values are allowed, and therefore this is valid:
struct Epsilon: Alpha {
enum Beta: Int {
case Zeta = 6
}
}
let epsilon = Epsilon()
Eta(alpha: epsilon, beta: .Zeta) // "beta is 6"
To address that I'm currently doing this:
protocol StringRawRepresentable: RawRepresentable {
class func fromRaw(raw: String) -> Self?
}
protocol Alpha {
typealias Beta: StringRawRepresentable
}
struct Gamma: Alpha {
enum Beta: String, StringRawRepresentable {
case Delta = "delta"
}
}
// Type 'Epsilon' does not conform to protocol 'Alpha'
struct Epsilon: Alpha {
enum Beta: Int {
case Zeta = 6
}
}
struct Eta<T: Alpha, U: StringRawRepresentable where T.Beta == U> {
let alpha: T
let beta: U
init(alpha: T, beta: U) {
self.alpha = alpha
self.beta = beta
println("beta is: \(beta.toRaw())")
}
}
let gamma = Gamma()
Eta(alpha: gamma, beta: .Delta) // "beta is delta"
Is there a way that I can declare the typealias
differently in the original example to restrict RawRepresentable
to String
?
Update
Specifying U: RawRepresentable where U.Raw == String
seemed hopeful, so I gave that a try:
protocol Alpha {
typealias Beta: RawRepresentable
}
struct Gamma: Alpha {
enum Beta: String {
case Delta = "delta"
}
}
struct Eta<T: Alpha, U: RawRepresentable where T.Beta == U, U.Raw == String> {
let alpha: T
let beta: U
init(alpha: T, beta: U) {
self.alpha = alpha
self.beta = beta
// Execution was interrupted, reason: EXC_BAD_ACCESS (code=EXC_I386_GPFLT).
println("beta is: \(beta.toRaw())")
}
}
let gamma = Gamma()
Eta(alpha: gamma, beta: .Delta) // "beta is delta"
struct Epsilon: Alpha {
enum Beta: Int {
case Zeta = 6
}
}
let epsilon = Epsilon()
Eta(alpha: epsilon, beta: .Zeta) // Error only occurs when this is executed
While this technically prevents using anything other than a String
, I'm looking for a compile-time constraint and this appears to be causing a runtime exception.
I'd also prefer that this be enforced by the protocol if possible rather than consumers needing to check that .Raw == String
Let's look at our options here. First, it's (as of Xcode 6 beta 5) a well known restriction that we cannot specify enum type constraints in the easy and expected way. Second, you need something very clear: to be able to call
fromRaw(String)
. And third, you want a compiler error. I'd say your best bet is to write a protocol to do exactly that, and push down to the consumer the requirement to ensure s/he gives you afromRaw(String)
. In this case, here's what I would do, simplifying your second code snippet:Philosophically this is imho more aligned with your needs: You say "I want something not only RawRepresentable, but also a
fromRaw(String)
. Figure out how you give it to me". The Gamma struct is the simplest example, where the consumer specifics his enum, and then says "ok, I can give you my standardfromRaw()
because it works.