- Original link: WWDC 2016: Increased Safety in Swift 3.0
- Original author: Matt Mathias
- Translation source: Juejin Translation Project
- Translator: Zheaoli
- Proofreaders: llp0574, thanksdanny
After the release of Swift, developers have emphasized that safety and optional types are among the most important features of Swift. They provided a representation mechanism for nil
and required a clear syntax for using instances that may be nil
.
Optional types mainly consist of the following two:
Optional
ImplicitlyUnwrappedOptional
The first approach is a safe one: it requires us to unwrap optional type variables to access the underlying value. The second approach is an unsafe one: we can directly access the underlying value without unwrapping the optional type variable. For example, using ImplicitlyUnwrappedOptional
when the variable value is nil
may lead to exceptions.
Below is an example of this issue:
let x: Int! = nil
print(x) // Crash! `x` is nil!
In Swift 3.0, Apple improved the implementation of ImplicitlyUnwrappedOptional
, making it safer than before. This raises the question: what improvements did Apple make to ImplicitlyUnwrappedOptional
in Swift 3.0 that enhance the safety of Swift? The answer lies in the optimizations Apple made in the compiler's type inference process for ImplicitlyUnwrappedOptional
.
Usage in Swift 2.x#
Let's understand the changes through an example.
struct Person {
let firstName: String
let lastName: String
init!(firstName: String, lastName: String) {
guard !firstName.isEmpty && !lastName.isEmpty else {
return nil
}
self.firstName = firstName
self.lastName = lastName
}
}
Here we created a struct Person
with a flawed initializer. If we do not provide values for first name
and last name
during initialization, the initialization will fail.
In init!(firstName: String, lastName: String)
, we used !
instead of ?
for initialization. Unlike Swift 3.0, in Swift 2.x, we used init!
to utilize ImplicitlyUnwrappedOptional
. Regardless of the version of Swift we are using, we should be cautious with init!
. Generally, if you can tolerate the exceptions that arise when referencing a generated instance that is nil
, then you can use init!
. Because using init!
when the corresponding instance is nil
will cause the program to crash.
In .*
, this initializer will generate an ImplicitlyUnwrappedOptional<Person>
. If initialization fails, all instances based on Person
will raise exceptions.
For example, in Swift 2.x, the following code will crash at runtime.
// Swift 2.x
let nilPerson = Person(firstName: "", lastName: "Mathias")
nilPerson.firstName // Crash!
Note that due to the implicit unwrapping in the initializer, we do not need to use type binding (translator's note 1: optional binding
) or optional chaining (translator's note 2: optional chaining
) to ensure that nilPerson
can be used normally.
New Approach in Swift 3.0#
In Swift 3.0, there is a slight change. The !
in init!
indicates that initialization may fail, and if initialization is successful, the generated instance will be forcefully implicitly unwrapped. Unlike Swift 2.x, the instance generated by init!
is optional
rather than ImplicitlyUnwrappedOptional
. This means you need to perform type binding or optional chaining for the instance based on different underlying values.
// Swift 3.0
let nilPerson = Person(firstName: "", lastName: "Mathias")
nilPerson?.firstName
In the above example, nilPerson
is an instance of type Optional<Person>
. This means that if you want to access the value inside normally, you need to unwrap nilPerson
. In this case, manual unwrapping is a very good choice.
Safe Type Declaration#
This change may be confusing. Why does using init!
generate an instance of type Optional
? Isn't it said that !
in init!
indicates generating ImplicitlyUnwrappedOptional
?
The answer lies in the relationship between safety and declaration. In the above code (let nilPerson = Person(firstName: "", lastName: "Mathias")
), the type of nilPerson
will depend on the compiler's inference.
In Swift 2.x, the compiler would treat nilPerson
as ImplicitlyUnwrappedOptional<Person>
. Logically, we have become accustomed to this compilation method, and it makes sense to some extent. In short, in Swift 2.x, if you wanted to use ImplicitlyUnwrappedOptional
, you needed to initialize the instance with init!
.
However, to some extent, this approach is quite unsafe. To be honest, we never explicitly indicated that nilPerson
should be an ImplicitlyUnwrappedOptional
instance, because if the compiler infers some unsafe type information in the future, leading to program errors, you would also bear some responsibility.
Swift 3.0 addresses this safety issue by treating ImplicitlyUnwrappedOptional
as optional
when we do not explicitly declare an ImplicitlyUnwrappedOptional
.
Restricting the Passing of ImplicitlyUnwrappedOptional
Instances#
One clever aspect of this approach is that it restricts the passing of implicitly unwrapped optional
instances. Referencing our earlier code about Person
, let's also consider some of our previous practices in Swift 2.x:
// Swift 2.x
let matt = Person(firstName: "Matt", lastName: "Mathias")
matt.firstName // `matt` is `ImplicitlyUnwrappedOptional<person>`; we can access `firstName` directly
let anotherMatt = matt // `anotherMatt` is also `ImplicitlyUnwrappedOptional<person>`
anotherMatt
is an instance of the same type as matt
. You may have anticipated that this is not an ideal situation. In the code, the instance of ImplicitlyUnwrappedOptional
has been passed. We must be cautious about the new unsafe code that arises.
For example, in the above code, what would happen if we performed some asynchronous operations?
// Swift 2.x
let matt = Person(firstName: "Matt", lastName: "Mathias")
matt.firstName // `matt` is `ImplicitlyUnwrappedOptional<person>`, and so we can access `firstName` directly
... // Stuff happens; time passes; code executes; `matt` is set to nil
let anotherMatt = matt // `anotherMatt` has the same type: `ImplicitlyUnwrappedOptional<person>`
In the above example, anotherMatt
is an instance with a value of nil
, which means any direct access to its underlying value will lead to a crash. This type of access is exactly what ImplicitlyUnwrappedOptional
recommends. So what if we replace anotherMatt
with Optional<Person>
? Would the situation be better?
Let's try the same code in Swift 3.0.
// Swift 3.0
let matt = Person(firstName: "Matt", lastName: "Mathias")
matt?.firstName // `matt` is `Optional<person>`
let anotherMatt = matt // `anotherMatt` is also `Optional<person>`
If we do not explicitly declare that we are generating an ImplicitlyUnwrappedOptional
, the compiler will default to using the safer Optional
.
Type Inference Should Be Safe#
The biggest benefit of this change is that the compiler's type inference does not compromise the safety of our code. If we choose some less safe methods when necessary, we must make explicit declarations. This way, the compiler will no longer make automatic judgments.
At times, if we indeed need to use an instance of type ImplicitlyUnwrappedOptional
, we only need to make an explicit declaration.
// Swift 3.0
let runningWithScissors: Person! = Person(firstName: "Edward", lastName: "") // Must explicitly declare Person!
let safeAgain = runningWithScissors // What’s the type here?
runningWithScissors
is an instance with a value of nil
because we provided an empty string for lastName
during initialization.
Note that the declared instance runningWithScissors
is an instance of ImplicitlyUnwrappedOptional<Person>
. In Swift 3.0, Swift allows us to use both Optional
and ImplicitlyUnwrappedOptional
. However, we must make explicit declarations to inform the compiler that we are using ImplicitlyUnwrappedOptional
.
Fortunately, the compiler no longer automatically treats safeAgain
as an ImplicitlyUnwrappedOptional
instance. Instead, the compiler will treat the safeAgain
variable as an Optional
instance. In this process, Swift 3.0 effectively limits the propagation of unsafe instances.
A Few Final Words#
The changes to ImplicitlyUnwrappedOptional
may stem from the fact that we often operate on APIs written in Objective-C on macOS or iOS, where certain return values may be nil
, which is unsafe for Swift.
Thus, Swift is working to avoid such unsafe situations. Many thanks to the Swift developers for their improvements to ImplicitlyUnwrappedOptional
. We can now write robust code more conveniently. Perhaps one day in the future, ImplicitlyUnwrappedOptional
will completely disappear from our view.
In Conclusion#
If you want to know more about this topic, you can find some useful information from this proposal. You can gain insights from the author's thoughts in the issue and learn more details through the specific changes. There are also links to related community discussions there.