Making types expressible by string interpolation
Basics article available: StringsCreating 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.