'Swift: Protocol `static var foo: Self` and enums

Simplified example

Take a look at this simple protocol

protocol FooOwner {
    static var foo: Self { get }
}

I would like an enum to conform to said protocol, and in my opinion this ought to work, since the static syntax SomeTypeConformingToFooOwner.foo should result in an instance of SomeTypeConformingToFooOwner and in the case where SomeTypeConformingToFooOwner is a enum.

enum Foo: FooOwner { // Type 'Foo' does not conform to protocol FooOwner
    case foo
}

The workaround is this ugly thing:

protocol FooOwner {
    static var fooOwner: Self { get }
}

enum Foo: FooOwner {
    case foo
    static var fooOwner: Foo {
        return Foo.foo
    }
}

Do you have a nicer workaround for enum conforming to protocols with static vars?

Real use case

protocol StringConvertibleError: Swift.Error {
    static var invalidCharactersError: Self { get }
}

protocol StringConvertibleErrorOwner {
    associatedtype Error: StringConvertibleError
}

protocol StringConvertible {
    var value: String { get }

    /// Calling this with an invalid String will result in runtime crash.
    init(validated: String)

    init(string value: String) throws

    static func validate(_ string: String) throws -> String
}

// MARK: - Default Implementation Constrained
extension StringConvertible where Self: CharacterSetSpecifying, Self: StringConvertibleErrorOwner {
    static func validate(_ string: String) throws -> String {
        guard Self.allowedCharacters.isSuperset(of: CharacterSet(charactersIn: string)) else {
            throw Error.invalidCharactersError
        }
        // Valid
        return string
    }
}

struct HexString: StringConvertible, CharacterSetSpecifying, StringConvertibleErrorOwner {

    static var allowedCharacters = CharacterSet.hexadecimal

    let value: String

    init(validated unvalidated: String) {
        do {
            self.value = try HexString.validate(unvalidated)
        } catch {
            fatalError("Passed unvalid string, error: \(error)")
        }
    }
}

extension HexString {
    enum Error: StringConvertibleError {
        static var invalidCharactersError: Error {
            return Error.invalidCharacters
        }

        case invalidCharacters
    }
}

So it's the last part that I would like to change to:

extension HexString {
    enum Error: StringConvertibleError {
        case invalidCharacters
    }
}

Since I have many similar types to HexString.

Yes of course obviously I can use one shared Error enum, but I would like to have one specific enum per type.



Solution 1:[1]

This might not fully match OP's issue, but as it seems somewhat related, I'm throwing it out there in case it helps. In my case, I cared about some cases, but other cases could be ignored (or handled in a common way). I admit my approach is not elegant and requires a lot of boiler plate, but it got me past a similar problem.

// Given these enums, we want a protocol that matches enums with
// 'foo' and 'bar' cases.
enum FooFriends {
    case foo // conforms
    case bar // conforms
    case baz // don't really care
}

enum FooBar {
    case foo // conforms
    case bar // conforms
}

// We wish we could do this:
protocol FooBarWish {
    case foo // or static var foo: Self { get }
    case bar // or static var bar: Self { get }
}

// Workaround
// the boiler plate
enum FooBarProxy {
    case foo
    case bar
    case other
}

protocol FooBarProtocol {
    func getProxy() -> FooBarProxy
}

extension FooFriends: FooBarProtocol {
    func getProxy() -> FooBarProxy {
        switch self {
        case .foo:
            return .foo
        case .bar:
            return .bar
        default:
            return .other
        }
    }
}

extension FooBar: FooBarProtocol {
    func getProxy() -> FooBarProxy {
        switch self {
        case .foo:
            return .foo
        case .bar:
            return .bar
        }
    }
}

// Usage
// Instead of the ideal case (which won't work)
let fooBarOrFooFriend1: FooBarWish = FooFriends.foo
let fooBarOrFooFriend2: FooBarWish = FooBar.bar

// We can get by with
let fooBarProxy1 = FooFriends.foo.getProxy()
let fooBarProxy2 = FooBar.bar.getProxy()

// Verification
func checkIt(_ foobar: FooBarProxy) {
    switch foobar {
    case .foo:
        print("it was foo")
    case .bar:
        print("it was bar")
    case .other:
        print("it was neither")
    }
}

checkIt(fooBarProxy1)
checkIt(fooBarProxy2)
// =>
// it was foo
// it was bar

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 forforf