Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In Swift, how can I specify a typealias that restricts RawRepresentable to String?

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

like image 713
Paul Young Avatar asked Aug 09 '14 19:08

Paul Young


1 Answers

Just to add onto this since it's a bit older, your updated example works in swift 2+ now and will complain at compile time that .Zeta is ambiguous unless it's a String type.

You can also put the check in a pattern match for a protocol extension. As an example:

extension SequenceType where Generator.Element:RawRepresentable,
                         Generator.Element.RawValue == String {
    func toStringArray() -> [String] {
        return self.map { $0.rawValue }
    }
}
like image 140
jimejim Avatar answered Oct 17 '22 06:10

jimejim