Why Type-Safety is Essential in Swift
What’s worse than crashing? Not crashing!
How can a program that crashes be better than a program that doesn’t crash.
This article explains what type safety means for Swift, and why that statement isn’t as crazy as it seems.
Difficulty: Beginner | Easy | Normal | Challenging (although the work on pointers later in the article can be tricky to understand)
- Coding in Swift Playgrounds (guide HERE)
Regarding type safety
Compiler: A program that converts instructions into a machine-code or lower-level form so that they can be read and executed by a computer
Type safety: The extent to which a programming language prevents type errors
Regarding Pointers (later in the article)
Pointer: An object that stores a memory address
UnsafePointer: A pointer for accessing data of a specific type
UnsafeRawPointer: A raw pointer for accessing untyped data. This means there is no type safety and no alignment
What is type safety
We can think of type safety in programming as:
- Blocking the ability to read from uninitialized variables
- Arrays cannot be accessed beyond their bounds
- Unchecked type casts are not possible
Example: an uninitialised variable
Swift will catch us if we try to use a variable before it’s initialized; even (in the case of the example below) there is a chance it can’t be initialized.
Keep to the right types
Example: Int and String
If you want to store a variable as a String, the fact that Swift is type-safe prevents you from passing an Integer (or any other type) to that variable.
let myStr: String = “Hello”
let strErr: String = 4 // error
This happens at compile time, and means that an end-user should never see such an error (which is great!)
Optionals are subject to the same type-safety; we can’t use an optional that requires a non-optional type.
let myStr: String? = “Hello”
var str: String = myStr // error
Even though the Swift compiler comes up with nots of nice solutions for us to solve the problem, it is still an error and once again stops us making a silly slip with our types!
Example: Force Unwrapping
It always seems unfortunate that a runtime crash can be caused by force-unwrapping an optional in Swift.
When we force-unwrap we equivalently say to the Swift compiler “I’ve got this — the variable or constant will never be nil”.
The compiler does not check this since it can’t know if the optional is nil; it might be dependent on user input or a random number so force unwrapping means that the compiler never checks.
let myStr: String? = nil
However Swift is still type-safe since the behaviour is defined.
Example: Reading outside the bounds of an Array
In C, by design, reading outside an array’s bounds is undefined (Since C favors performance over safety — this is why many OS’s run fast). In Swift, our type-safe language, this is not true. This means that the following gives an error at runtime:
let names = [“Paven”,”Kim”,”Dave”]
names // error
The error given (
EXC_BAD_INSTRUCTION) is a defined behaviour, which means we can’t access elements from outside the array.
The advantage of type safety
Your end user should never see such errors, they should be caught by the programmer. That is, the error should be caught in advance of even a (theoretical) tester being exposed to your work.
This is a Bingo, since the error is caught so early in the development process that the programmer can fix a slip before anyone else is even aware of it; that is it is so early in the process.
What if we weren’t in a type safe world
You can move into a world that is not type-safe, even in Swift.
One way to approach this is with pointers, where we can actually rebind memory to a different type and no type checking takes place.
Bind to (the wrong?) type
We take a String (“Paven”) and create a pointer to that instance (using an UnsafePointer to do so). We can then bind this to a different type!
On my machine namePointer points to memory location
0x0000000124c21140 and the String will be at that location.
Strings are stored by Swift’s Unicode Scalar Values which is a 21-bit number. However as ascii
A is equivalent to 65 in denary. Swift’s String type is said to be built from Unicode Scalar Values. In Swift this is a 21-bit number (All this is covered in another article).
This pointer is cast to another type — this type is from String to Integer.
Both the original pointer and the
intPointer refer to the same location; but we are interpreting the bytes at that point as an Integer rather than a String.
We can even see the elements from the bytes by using the following code:
Which is a representation of
UnsafeRawBufferPointer(start: 0x00000001228ed140, count: 16) and returns the following bytes:
Which makes sense as, surprise surprise, the ASCII code for A is 65. Please do note that short strings are stored directly at a Pointer, but longer strings may not be (and since String is a struct, this is why 65 is at the first location in the array as this is not a simple representation of the ASCII code).
As you can see, an unchecked type cast has just been performed! If the types are different sizes we are just asking for trouble as we could be reading…which memory location again?
Type safety is really important since it means we have defined behaviour for operations. This allows us to have a safe language, not just for making sure that types are (in some respect) correct but stops various vectors of attack (like accessing outside the bounds of an array).
We can even go so far as to use pointers, perhaps even unsafePointers to sidestep Swift’s type safety if we so choose.
I hope this tutorial has given you some idea of what this means, and some feeling of under the working under the hood of Swift.
The Twitter contact:
Any questions? You can get in touch with me HERE