Weekly Swift articles, podcasts and tips by John Sundell.

Making types expressible by string interpolation

Published on 27 Sep 2019
Basics article available: Strings

Creating wrapper types for raw values, such as strings and integers, can be a great way to make our code a bit more type-safe and self-documenting — and also gives us dedicated types on which we can implement domain-specific convenience APIs.

For example, here’s such a type that represents a file system path, which can be used to do things like load the contents of a file:

struct Path {
    var string: String
}

func loadFile(at path: Path) throws -> File {
    ...
}

However, while having that dedicated Path type does give us several benefits, it could also make our APIs a bit more cumbersome to use. For example, anytime we want to specify a path using a string literal, we now have to wrap it first:

try loadFile(at: Path(string: "~/documents/article.md"))

To fix that, we can make Path conform to ExpressibleByStringLiteral, which enables us to directly pass a string literal to any API that accepts a Path:

extension Path: ExpressibleByStringLiteral {
    init(stringLiteral value: String) {
        self.string = value
    }
}

try loadFile(at: "~/documents/article.md")

That’s really nice, however, the above won’t work if we’re using any form of interpolation within our string — such as in this case:

// Since this string literal contains interpolation, it won't
// be automatically converted to a Path, and we'll get an error:
try loadFile(at: "/users/\(username)/file.txt")

Thankfully, the above problem is easily fixed. All we have to do is to declare that our Path also conforms to ExpressibleByStringInterpolation — and the compiler will be able to infer the rest:

extension Path: ExpressibleByStringInterpolation {}

We can now express our Path type using any kind of string literal — which makes it much more convenient to use, while still giving us all of the benefits of stronger typing.