Search code examples
swiftswitch-statementprotocolscontrol-flow

Swift rewriting init(format: String, _ arguments: CVarArg...)


I'm trying to rewrite the standard String(format, arguments) method found in foundation which takes a string and replaces all values containing %i with ints and %@ with strings and a whole range of types. https://developer.apple.com/documentation/swift/string/3126742-init

Since I don't know c I converted the initializer from

init(format: String, _ arguments: CVarArg) {

to

init(format: String, _ arguments: [Any]) {

Now I got it working for Ints using this in a String Extension

  init(format: String, _ arguments: [Any]) {
            var copy = format
            for argument in arguments {
                switch argument {
                case let replacementInt as Int:
                    String.handleInt(copy: &copy, replacement: String(replacementInt))
                default:
                    self = format
                }
            }
        self = copy
    }

private static func handleInt(copy: inout String, replacement: String) {

but since I would like this to work for all values I'm trying to use the switch to look for type Any that has the Protocol of LosslessStringConvertible required to convert to a string using the String(value) initializer.

     init(format: String, _ arguments: [Any]) {
        var copy = format
        for argument in arguments {
            switch argument {
            case let replacementInt as LosslessStringConvertible:
                String.handleAnyValue(copy: &copy, replacement: String(replacementInt))
            default:
                self = format
            }
        }
    self = copy
}

However, I get the following error when applying String(replacementInt)

Protocol type 'LosslessStringConvertible' cannot conform to 'LosslessStringConvertible' because only concrete types can conform to protocols

Bonus A bonus would be if I could do this without importing any library and simply writing using swift.


Solution

  • You could make conformance to LosslessStringConvertible to be a requirement for arguments:

    init<S: LosslessStringConvertible>(format: String, _ arguments: [S])
    

    This would support all types that conform that protocol out of the box (and allows to extend other types to conform this protocol):

    var x: String  = String(format: "%i %@", 5, "five")
    print(x) // prints "5 five"
    

    Limitation with this solution is that for instance type that doesn't conform to LosslessStringConvertible will cause an error. For example:

    class Z {}
    let z = Z()
    var y: String = String(format: "%i %@ %@", 5, "five", z) // Compilation error: Argument type 'Z' does not conform to expected type 'CVarArg'