Expressible literals in Swift explained by 3 useful examples

Expressible literals allow you to initialize types by making use of literals. There are multiple protocols available in the Swift standard library and chances are big that you’ve already been using one of those.

An example is the ExpressibleByStringLiteral allowing us to initialize a String using surrounding double quotes instead of using the String(init:) method. We can all benefit from the fact that Swift is built through these protocols by adopting the protocols in our own defined types.

What is a literal?

A literal is a notation for representing a fixed value such as integers, strings, and booleans. Literals in Swift are made possible by several available protocols. Standard types conform to these protocols and allow us to initialize values as follows:

var integer = 0 // ExpressibleByIntegerLiteral
var string = "Hello!" // ExpressibleByStringLiteral
var array = [0, 1, 2] // ExpressibleByArrayLiteral
var dictionary = ["Key": "Value"] // ExpressibleByDictionaryLiteral
var boolean = true // ExpressibleByBooleanLiteral

Those are the most commonly used but a few more exist. It’s worth exploring and while you do, you might run into a special protocol called ExpressibleByNilLiteral.

The ExpressibleByNilLiteral is used to make it possible to initialize optionals using nil:

var optional: String? = nil // ExpressibleByNilLiteral

This protocol is used for optionals only and should not be used for custom types. Basically, just ignore this one.

Adding literal support to custom types

In most cases, you’ll be fine by making use of the default integrated adoption of the protocols. However, it could be that you want to add custom literal support to existing types or to custom types defined in your project.

To explain how this works we’ll use the example of adding string literal support to URLs. This allows us to initialize a URL directly from a string.

To do this, we need to adopt the ExpressibleByStringLiteral protocol for the URL type:

extension URL: ExpressibleByStringLiteral {
    public init(stringLiteral value: StaticString) {
        self.init(string: "\(value)")!
    }
}

It’s worth pointing out the limitations here as we can’t create a failable or throwing initializer. This requires us to force unwrap the created URL which might not always be what you want. If so, you can always fall back to using the URL(string:) initializer directly.

If it makes sense to force unwrap in your case you can start using the literal as follows:

let url: URL = "https://www.avanderlee.com"

This can simplify the creation of URLs, especially if you’re sure that the URL is valid. Note that we’re making use of a StaticString type so that dynamic strings aren’t supported. Dynamic strings would add extra complexity like URL encoding parameters. You could support this but it’s not the focus of this blog post.

Different types of strings

When you add support for string literals you should consider adopting the ExpressibleByUnicodeScalarLiteral for Unicode scalar support and ExpressibleByExtendedGraphemeClusterLiteral for supporting extended grapheme clusters. An extended grapheme cluster is a group of one or more Unicode scalar values that become a single visible character.

If you want to support all three cases at once you can consider adding the following extensions:

extension ExpressibleByUnicodeScalarLiteral where Self: ExpressibleByStringLiteral, Self.StringLiteralType == String {
    public init(unicodeScalarLiteral value: String) {
        self.init(stringLiteral: value)
    }
}

extension ExpressibleByExtendedGraphemeClusterLiteral where Self: ExpressibleByStringLiteral, Self.StringLiteralType == String {
    public init(extendedGraphemeClusterLiteral value: String) {
        self.init(stringLiteral: value)
    }
}

Useful examples of using literals in custom types

To inspire you how you can make use of literals in your custom types I’ll show you a few examples that you might find useful.

Initializing dates directly from a string

When you’re often using the same date format you can initialize your dates from a String directly:

extension Date: ExpressibleByStringLiteral {
    public init(stringLiteral value: String) {
        let formatter = DateFormatter()
        formatter.dateFormat = "dd-MM-yyyy"
        formatter.locale = Locale(identifier: "en_US_POSIX")
        formatter.timeZone = TimeZone(secondsFromGMT: 0)
        self = formatter.date(from: value)!
    }
}
let date: Date = "09-12-2020"
print(date) // Prints: 2020-12-09 00:00:00 +0000

Initializing a custom cache from a dictionary

If you defined your own custom caching store you might want to initialize it directly from a dictionary:

final class Cache<T> {
    private var store: [String: T]

    init(objects: [String: T]) {
        self.store = objects
    }

    func cache(_ object: T, forKey key: String) {
        store[key] = object
    }

    func object(for key: String) -> T? {
        return store[key]
    }
}

extension Cache: ExpressibleByDictionaryLiteral {
    convenience init(dictionaryLiteral elements: (String, T)...) {
        self.init(objects: [String: T](uniqueKeysWithValues: elements))
    }
}

let cache: Cache<URL> = ["SwiftLee": URL(string: "https://www.avanderlee.com")!]
print(cache.object(for: "SwiftLee")!) // Prints: https://www.avanderlee.com

Initializing custom auto-layout priorities

When you’re writing auto-layout in code you might need to set a custom layout priority:

extension UILayoutPriority: ExpressibleByIntegerLiteral {
    public init(integerLiteral value: Int) {
        self.init(rawValue: Float(value))
    }
}

let superview = UIView()
let view = UIView()
let constraint = view.leadingAnchor.constraint(equalTo: superview.leadingAnchor)
constraint.priority = 200 // We can now set the priority directly

Note that we can’t make use of the ExpressibleByFloatLiteral protocol as the input value will always be seen as an integer. Therefore, we need to make use of the ExpressibleByIntegerLiteral protocol.

Conclusion

That was it! Hopefully, you’ve been inspired by what you can do with literals in Swift. You might have your own custom types that go great together with the available literal protocols.

If you like to improve your Swift knowledge, even more, check out the Swift category page. Feel free to contact me or tweet to me on Twitter if you have any additional tips or feedback.

Thanks!