2 December 2021 4min read

Swift's default literal types

Today I want to show you a rather obscure feature of Swift. You will learn a bit about Swift’s type system, type inference and other nice powers that the language has.

We know Swift is a strongly typed programming language, that means that every variable you declare has a type defined at compile time.

let name: String = "Kelsier"

The name variable has a specific type: String. But, as with any modern language, having to define the types of every single variable can be tedious and so the compiler infers the types for us.

So you can leave them out in most places:

func someAge() -> Int {
    return 1
}
let age = someAge() // inferred to be Int

The compiler can see that the function someAge returns an Int and thus the variable age must be of type Int. Something that any Swift programmer is used to.

Literals

But let’s go back to the original example and rewrite it to use type inference:

let name = "Kelsier"

Now the type of name is not explicitly declared by the programmer. Which means that the compiler needs to infer it from the right side of the assignment, a string literal.

Now the type of name is not explicitly declared by the programmer. So the compiler needs to infer it from the right side of the assignment, a string literal.

And of course the compiler is going to define the variable as a String. Or will it?

The peculiarity of string literals is that they don’t have an intrinsic type on their own. The context defines their type. For example, let’s look at the number literals.

func iWantAnInt(_ i: Int) {}
func iWantADouble(_ d: Double) {}
iWantAnInt(42)
iWantADouble(42)

Here you can see how the literal 42 is inferred to be an Int or a Double depending on how it’s used.

Expressible By Literal

Another interesting feature of the language is that it allows us to make our own types be used with literals. Every literal in the language has an associated protocol in the form of ExpressibleBy*Literal. If your type conforms to that protocol, a literal can be used to initialise it.

For example, let’s make our own number:

struct MyNumber: ExpressibleByIntegerLiteral {
    init(integerLiteral value: IntegerLiteralType) {
        // ...
    }
}

func iWantMyNumber(_ d: MyNumber) {}
iWantMyNumber(42)

We can see how our type behaves the same as the standard library types in terms of inference. That’s very powerful!

Default literal types

So now that we understand how type inference, let’s refer to the original example again and ask, which is the type of name?

let name = "Kelsier"

For this, the language needs to base the inference in the context, and since we’re not passing the variable to a function that has an explicit parameter type, the compiler must use some safe default instead. And of course, the best default for a string literal is the type String. So we have the answer!

name is of type String.

… or is it?

One of the best things about Swift’s design is to have as much as possible on user land, as opposed to specifically coded in the compiler. I just told you that String is the default type for the string literal, and that’s true, but that knowledge is defined in the standard library. Is not a hard-coded decision in the compiler.

So how does the standard library define the default types for literals? Look for *LiteralType. In the string case StringLiteralType is defined as:

typealias StringLiteralType = String

This means that we can define our own typealias with the same name and change the default literal in our context.

typealias StringLiteralType = MyString

struct MyString: ExpressibleByStringLiteral {
    init(stringLiteral value: String) { 
      // ....
    }
}

And now, for the last time, what’s the type of name?

let string = "hola"
print(type(of: string)) // MyString

Since the compiler sees our definition of StringLiteralType (since is in the same module) from now on, every literal string in our code will be of type MyString.

Conclusion

In this post I explained a rather obscure feature of the Swift language. It’s not something you will see being used pretty much anywhere, but is good to know we can change the default types of literals. It makes the language very powerful. Of course we need to be careful with over using a power, unless is to prank your coworkers for a bit 😉

Tell me, have you used this in a real codebase?

If you enjoyed this post

Continue reading