Buy the PDF

Chapter 5 Static Typing and Type Inference

We’ve already touched on Swift’s type system a couple of times - if you’re coming from a dynamically typed language it’s one of the most immediately noticeable differences with Swift. It’s such an important part of the language that it’s time to dive in deeper before we go any further.

Swift is statically, strongly typed and uses type inference to determine the initial type of all your variables and constants.

5.1 Statically typed

Being statically typed means that all your variables, constants, functions must have their types declared in advance. Then the compiler uses these type declarations to check that there are no type errors when it compiles the program. If there is a type error, your program won’t compile.

All your variables must have a type, and once declared (or inferred) that type cannot change. Trying to change the type of something will result in an error.

In this example, we declare an integer variable (Swift infers that it’s an integer from our initial assignment). Trying to assign a string value to our variable will cause Swift’s to raise a type error.

var i = 1
i = "One" // Error

5.2 Type inference

As we’ve mentioned already Swift uses type inference to infer what types your variables have. Alternatively you can explicitly declare the type of your variables but in practice you often don’t need to. Swift will infer the type of a var if you assign it an initial value. So when you declare a variable, you need to either

  1. give it an initial value (which Swift will use to infer its type ), or
  2. explicitly declare its type.

Here’s an example of how we can declare a type explicitly.

1 var s = "A string"
2 var s2:String
3 s2 = "Another string"

In line 1 we declare a string variable and give it in initial value. Because we gave it an initial value, we don’t need to explicitly declare the type of the variable - Swift infers that this is a string. In line 2 we see how to initialize a variable without giving it an initial value. In this case, because we didn’t give the variable an initial value, Swift can’t infer its type, so we need to explicitly define its type as a String. At line 3 then we can assign a string value to that variable. If we tried to assign a value of a different type we would get an error.

5.3 Strongly typed

Swift is strongly typed. Whenever you use a variable or pass something as a function argument, Swift checks that it is of the correct type. You can’t pass a string to a function that expects an integer etc. Swift does this check at compile time (since it’s statically typed).

Here’s an example where we declare a function (we’ll go into functions more detail in chapter 11). A function has a type-signature and anytime you call the function, your function call must correspond to that type-signature. The type-signature for a function is the type of its individual parameters along with its return type.

1 func adder(x:Int, y:Int) -> Int {
2     return x + y
3 }
5 adder(1, y: 3)   // 4
6 adder(1.0, y: 3.0) // Error

The type-signature for this function indicates that it takes 2 arguments, both of which are integers and it returns an integer ( -> Int indicates the return type of the function)

If we try to call this function with 2 integers as arguments (line 5) it returns an integer. But if we try to call it with arguments that aren’t both integers we’ll get an error (line 6). And if we tried to return a value from within the function that isn’t an integer we would also get a compiler error as the type signature for the function specifies that it returns an integer.

5.4 Type Safety

Swift is a type-safe language. All variables have a declared type. All functions/methods have a type-signature which declares the types of its arguments and the type of what it returns. The Swift compiler checks at compile time that all your types match up and your program won’t compile if they don’t.

Swift uses the type-signature of your functions and methods to make sure they are being called correctly (i.e. with the correct types). It also uses type-signature for function dispatch. So you can define multiple versions of a function with different types-signatures, and the one that gets called is the one where the arguments type-signatures match. Here’s an example where we defining two functions with the same name, but different type-signatures. If we call call the function with floats as arguments, the first one gets called, and if we call it with integer arguments, the second one gets called.

func adder(x:Float, y:Float) -> Float{
    print("Adding Floats")
    return x + y

func adder(x:Int, y:Int) -> Int {
    print("Adding Ints")
    return x + y
adder(1, y: 3)   // 4
adder(1.0, y: 3.0)  // 4.0

5.5 Type Aliases

Type aliases let you declare a name for a compound type. In chapter 8 we will see how to use the typealias keyword to declare an alias for a type that is a tuple.

5.6 Generics

Having functions or methods that can act on different types is tremendously useful. So we don’t want to give that up entirely. We don’t want to have to implement multiple versions of a function for multiple different types. For that swift provides generics, which support the concept of generic programming. This lets you write functions that work with any type that conforms to a specific interface/protocol. We’ll cover both protocols and generics in later chapters.

5.7 The benefits of static typing

One of the things I liked when I started programming in Python and Ruby was that I didn’t need to declare the type of everything. Just being able to assign and use a variable when I needed it felt freeing, and the lack of a compilation step really seemed to speed up development. Later I learned that you don’t really gain much in the long term. That initial boost in development speed gets eaten up later in the project, when you’re debugging runtime errors, writing extensive test suites, or trying to understand the shape of the data-structure that a function uses.

Having to declare your types and satisfy the compiler’s type system is not as onerous as it may appear. Since I started using languages with good, strong type systems, it’s become something that I miss when I’m programming in dynamic languages. A few points on the benefits/costs of static typing:

  • In dynamically typed languages, you still need to be aware of the types of the objects (along with how they behave) that your are passing around your program. Often passing the wrong type of object to a method that expects something else will result in an error (which you won’t notice until your program is running). In fact by not explicitly declaring and designing your types, you increase the cognitive workload associated with keeping track of all the objects you’re passing around your program.
  • You don’t actually save that much. In the grander scheme of things, declaring the types of things in your program is a tiny portion of the overall amount of work involved in building a working program. And whatever time and typing you end up saving by not having to declare your types, you’ll end up using it to write unit tests and writing documentation for your function arguments anyway.
  • With static typing you end up with less meta-programming power. For some problems this means that you’ll end up with a solution that is more explicit and less “magicy”. For larger systems and long-term maintenance and readability the absence of magic can actually be a benefit.
  • You can often deduce what a function does from its type-signature. This is very useful for making code more readable and understandable.
  • Refactoring becomes easier, as you have a bunch of compiler errors to tell you where you broke things, and you have the type-signatures of each function to help you understand what it does.
  • In Swift you don’t give up on all the advantages of being able to develop without a compile step. The fact that you have a repl and playgrounds give you many of the benefits that come with dynamic languages by removing the historically slow and annoying step of having to constantly compile your code to see if works.