Swift is commonly described as a “safe” language. Indeed, the About page of swift.org says:
Swift is a general-purpose programming language built using a modern approach to safety, performance, and software design patterns.
and
Safe. The most obvious way to write code should also behave in a safe manner. Undefined behavior is the enemy of safety, and developer mistakes should be caught before software is in production. Opting for safety sometimes means Swift will feel strict, but we believe that clarity saves time in the long run.
Fast. Swift is intended as a replacement for C-based languages (C, C++, and Objective-C). As such, Swift must be comparable to those languages in performance for most tasks. Performance must also be predictable and consistent, not just fast in short bursts that require clean-up later. There are lots of languages with novel features — being fast is rare.
Expressive. Swift benefits from decades of advancement in computer science to offer syntax that is a joy to use, with modern features developers expect. But Swift is never done. We will monitor language advancements and embrace what works, continually evolving to make Swift even better.
For example, when working with things like the Optional
type, its clear how Swift increases safety. Before, you would never know which variables could be null and which couldn’t. With this new nullability information, you’re forced to handle the null case explicitly. When working with these “nullable” types, you can opt to crash, usually using an operator that involves an exclamation point (!
). What is meant by safety here is apparent. It’s a seatbelt that you can choose to unbuckle, at your own risk.
However, in other cases, the safety seems to be lacking. Let’s take a look at an example. If we have a dictionary, grabbing the value for some given key returns an optional:
let person: [String: String] = //...
type(of: person["name"]) // => Optional<String>
But if we do the same with an array, we don’t get an optional:
let users: [User] = //...
type(of: users[0]) // => User
Why not? The array could be empty. If the users
array were empty, the program would have no real option but to crash. That hardly seems safe. I want my money back!
Well, okay. Swift has an open development process. Perhaps we can suggest a change to the swift evolution
mailing list, and—
Nope, that won’t work either. The “commonly rejected” proposals page in the swift-evolution
GitHub repo says that they won’t accept such a change:
- Make
Array<T>
subscript access returnT?
orT!
instead ofT
: The current array behavior is intentional, as it accurately reflects the fact that out-of-bounds array access is a logic error. Changing the current behavior would slowArray
accesses to an unacceptable degree. This topic has come up multiple times before but is very unlikely to be accepted.
What gives? The stated reason is that speed is too important in this particular case. But referring back to the About page linked above, “safe” is listed as a description of the language before “fast”. Shouldn’t safety be more important than speed?
There is a fundamental contention here, and the solution lies in the definitions of the word “safe”. While the common understanding of “safe” is more or less “doesn’t crash”, the Swift core members usually use the same word to mean “will never access incorrect memory unintentionally”.
In this way, Swift’s Array
subscript is “safe”. It’ll never return data in memory beyond the bounds allocated for the array itself. It will crash before giving you a handle on memory that doesn’t contain what it should. In the same way that the Optional type prevents whole classes of bugs (null dereferencing) from existing, this behavior prevents a different class of bugs (buffer overflows) from existing.
You can hear Chris Lattner make this distinction at 24:39 in his interview with ATP:
We said the only way that this can make sense in terms of the cost of the disruption to the community is if we make it a safe programming language: not “safe” as in “you can have no bugs,” but “safe” in terms of memory safety while also providing high performance and moving the programming model forward.
Perhaps “memory-safe” is a better term than just “safe”. The idea is that, while some application programmers might prefer getting back an optional instead of trapping on out-of-bounds-array access, everyone can agree that they’d prefer to crash their program rather than let it continue with a variable that contains invalid data, a variable that could potentially be exploited in a buffer overflow attack.
While this second tradeoff (crashing instead of allowing buffer overflows) may seem obvious, some languages don’t give you this guarantee. In C, accessing an array out-of-bounds gives you undefined behavior, meaning that anything could happen, depending on the implementation of the compiler that you were using. Especially in cases when the programmer can quickly tell that they made a mistake, such as with out-of-bounds array access, the Swift team has shown that they feel like this is an acceptable place to (consistently!) crash, instead of returning an optional, and definitely instead of returning junk memory.
Using this definition of “safe” also clarifies what the “unsafe” APIs are designed for. Because they muck about in memory directly, the programmer herself has to take special care to ensure that she’ll never allow access to invalid memory. This is extremely hard, and even experts get it wrong. For an interesting read on this topic, check out Matt Gallagher’s post on bridging C to Swift in a safe fashion.
Swift and the core team’s definition of “safe” may not line up 100% with yours, but they do prevent classes of bugs so that programmers like you don’t have to think about them day-to-day. It can often help to replace their usage of “safe” with “memory safe” to help understand what they mean.