Alexito's World

A world of coding 💻, by Alejandro Martinez

Generalising Swift string interpolation

In the first article of this series we made a safe string type that was safe to use but with a cumbersome API, and on the second article we made that API much nicer to Swift 5 interpolation. On this post we will generalise that conformance to make it easy to create new safe languages.

Making XML a safe string

As an example let's use XML.

struct XML: Language {
    let name = "XML"
    
    var value: String = ""
    
    mutating func appendFragment(_ fragment: String) {
        value.append(fragment)
    }
    
    mutating func appendText(_ text: String) {
        value.append(escapeXML(xml: text))
    }
    
    private func escapeXML(xml: String) -> String {
        return CFXMLCreateStringByEscapingEntities(nil, xml as CFString, nil) as String
    }
}

Don't take this implementation as production ready as it's just for example purposes.

Now we can create a safe version of an XML string combining fragments of unsafe text that will be properly escaped.

var xml = XML()
xml.appendFragment("<b>")
xml.appendText("danger & warnings")
xml.appendFragment("</b>")
xml.value // "<b>danger &amp; warnings</b>"

Protocols to the rescue

Thanks to powerful Swift protocols we can easily generalise the String interpolation conformance from the previous posts directly into the Language protocol.

protocol InterpolableLanguage: Language, ExpressibleByStringLiteral, StringInterpolationProtocol, ExpressibleByStringInterpolation {
    init()
}

ExpressibleByStringInterpolation already brings ExpressibleByStringLiteral so that conformance is redundant. Is just displayed here for clarity.

I decided here to create a new protocol InterpolableLanguage because we can't extend the Language protocol and because we will need an initialiser to be available; I didn't want to pollute the basic Language protocol.

There are many other design possibilities. From creating a generic type SafeString like the original post, or even using more protocols and extensions. It would be an interesting design space for anybody interested in making a library with these concepts. Now let's implement all the conformances in extensions to that protocol. This code looks identical at the one from the previous post, except now is implemented in the protocol ```swift extension InterpolableLanguage { // ExpressibleByStringLiteral init(stringLiteral value: String) { self.init() self.appendText(value) } } extension InterpolableLanguage { // StringInterpolationProtocol init(literalCapacity: Int, interpolationCount: Int) { self.init() } mutating func appendLiteral(_ literal: String) { self.appendFragment(literal) } mutating func appendInterpolation(unsafe text: String) { self.appendText(text) } mutating func appendInterpolation(safe text: String) { self.appendFragment(text) } } extension InterpolableLanguage { // ExpressibleByStringInterpolation init(stringInterpolation: Self) { self.init() appendFragment(stringInterpolation.value) } } ``` This is one of the beauties of Swift. Going from code that was specifically written for a type and generalising it to a lot of types thanks to protocol extensions. Now we can make our new `XML` type get all these features with a single line: ```swift extension XML: InterpolableLanguage {} ``` And with only that line we get all the interpolation capabilities: ```swift let xml: XML = "\(unsafe: "danger & warnings")" xml.value // "danger & warnings" ``` And thanks to the `safe` and `unsafe` overloads we can combine XML strings easily: ```swift let startP = "

" let endP = "

" let final: XML = """ \(safe: startP) \(unsafe: "danger & warnings") \(safe: endP) """ ``` We can even use Swift multiline strings with it! ## Conclusion In this post we've seen how Swift makes it easy to get a powerful feature implemented for a specific type and generalise it to many more. Now we can create safe string languages easily and without much effort. In the [next article](/blog/safe-string-example-safe-xml-and-url-part-4/) we will reimplement the original example from years ago with this new and updated system.

If you liked this article please consider supporting me