Swift protocol for things that convert to and from String

1.5k views Asked by At

I'd like to have a GenericThing with a template parameter that is any type that can sensibly be converted to and from a string.

// ConvertsToAndFromString is a made up protocol here – what should I use instead?
struct GenericThing<Id: ConvertsToAndFromString> {
}

I should then be able to use GenericThing with any type that has a reasonable encoding as a string. For example, it should work for Int, String (well, dah), and ideally, any RawRepresentable where the RawValue itself will convert to and from a string.

Example:

enum Tubbies: String {
  case dipsy
  case laalaa
  case po
}

// I'd like to be able to do this.
let genericThing = GenericThing<Tubbies>

I can't see how to easily do this.

I was hoping I could use LosslessStringConvertible instead of my made up ConvertsToAndFromString.

I tried this, and it works for Int and such. But it doesn't work for Tubbies. I couldn't see a way to make all RawRepresentable where RawValue: LosslessStringConvertible also conform to LosslessStringConvertible.

2

There are 2 answers

10
Alexander On BEST ANSWER

This is how you extend RawRespresentable to be conditionally LosslessStringConvertible depending on its RawValue:

extension RawRepresentable where RawValue: LosslessStringConvertible {
    init?(_ rv: RawValue) {
        self.init(rawValue: rv)
    }

    var description: String { return self.rawValue.description }
}

Here it is in action:

struct GenericThing<Id: LosslessStringConvertible> {

}

enum Tubbies: String, LosslessStringConvertible {
    case dipsy
    case laalaa
    case po
}

let genericThing = GenericThing<Tubbies>()
print(Tubbies.po is LosslessStringConvertible) // => true
0
Joe On

Had an issue in Swift 5.2 at least where extending RawRepresentable was causing CodingKeys to fail compiling.

public extension RawRepresentable where RawValue: LosslessStringConvertible {
    init?(_ rv: RawValue) { self.init(rawValue: rv) }
    var description: String { rawValue.description }
}

struct Test: Codable {
    public var test: String
    enum CodingKeys: String, CodingKey { // Error: Type 'Test.CodingKeys' does not conform to protocol 'CustomStringConvertible'
        case test = "foo"
    } 
}

My workaround was to instead explicitly add conformance using the same strategy, it requires being able to change the enum but allows CodingKeys to compile.

public protocol LosslessStringConvertibleEnum: LosslessStringConvertible, 
    RawRepresentable where RawValue: LosslessStringConvertible {}

public extension LosslessStringConvertibleEnum {
    init?(_ rawValue: RawValue) { self.init(rawValue: rawValue) }
    var description: String { rawValue.description }
}

enum Tubbies: String, LosslessStringConvertibleEnum {
    case dipsy
    case laalaa
    case po
}