NEW: Subscribe to Hacking with Swift+ and accelerate your learning! >>

Swift 5.0 is changing optional try

…and it could break your code.

Paul Hudson       @twostraws

The Swift team have worked hard to maintain source compatibility since Swift 3.0, but Swift 5.0 is bringing with it one important change that is guaranteed to break some projects: the way try? handles optionals is changing.

SE-0230 modifies the way try? works so that nested optionals are flattened to become regular optionals. This makes it work the same way as optional chaining and conditional typecasts, both of which flatten optionals in earlier Swift versions.

Here’s a practical example that demonstrates the change:

struct User {
    var id: Int

    init?(id: Int) {
        if id < 1 {
            return nil
        }

        self.id = id
    }

    func getMessages() throws -> String {
        // complicated code here
        return "No messages"
    }
}

let user = User(id: 1)
let messages = try? user?.getMessages()

The User struct has a failable initializer, because we want to make sure folks create users with a valid ID. The getMessages() method would in theory contain some sort of complicated code to get a list of all the messages for the user, so it’s marked as throws; I’ve made it return a fixed string so the code compiles.

The key line is the last one: because the user is optional it uses optional chaining, and because getMessages() can throw it uses try? to convert the throwing method into an optional, so we end up with a nested optional. In Swift 4.2 and earlier this would make messages a String?? – an optional optional string – but in Swift 5.0 and later try? won’t wrap values in an optional if they are already optional, so messages will just be a String?.

This new behavior matches the existing behavior of optional chaining and conditional typecasting. That is, you could use optional chaining a dozen times in a single line of code if you wanted, but you wouldn’t end up with 12 nested optionals. Similarly, if you used optional chaining with as?, you would still end up with only one level of optionality, because that’s usually what you want.

Even though this is a breaking change, the team worked hard to outline why it was worth doing:

  1. Most folks are explicitly flattening the optional by hand right now, so it wouldn’t break their code as much as make it partly redundant.
  2. This brings try? in line with as? and optional chaining, making the language more consistent.
  3. Automated migration can help move folks across if the change breaks their code.

It’s really great to see the developers working so hard to figure out what code might break and mitigate problems as best as possible.

Hacking with Swift is sponsored by Paw

SPONSORED Use Paw to build, test and describe web APIs. Paw has a lightning fast native macOS interface to compose requests, collaborate in real-time on API specs, and generate client code for your applications. You can import and export API definitions.

Discover Paw for Mac

Sponsor Hacking with Swift and reach the world's largest Swift community!

BUY OUR BOOKS
Buy Pro Swift Buy Swift Design Patterns Buy Testing Swift Buy Hacking with iOS Buy Swift Coding Challenges Buy Swift on Sundays Volume One Buy Server-Side Swift (Vapor Edition) Buy Advanced iOS Volume One Buy Advanced iOS Volume Two Buy Advanced iOS Volume Three Buy Hacking with watchOS Buy Hacking with tvOS Buy Hacking with macOS Buy Dive Into SpriteKit Buy Swift in Sixty Seconds Buy Objective-C for Swift Developers Buy Server-Side Swift (Kitura Edition) Buy Beyond Code

About the author

Paul Hudson is the creator of Hacking with Swift, the most comprehensive series of Swift books in the world. He's also the editor of Swift Developer News, the maintainer of the Swift Knowledge Base, and a speaker at Swift events around the world. If you're curious you can learn more here.

Was this page useful? Let us know!

Average rating: 4.8/5

Link copied to your pasteboard.