@ericasadun Erica, any idea of best practises w/ throws vs nil? When should I use nil returns? When throw? Thanks!
— Bas Broek (@BasThomas) July 27, 2015
It is tempting to use optionals to signal when an operation has failed. The following snippet represents Swift prior to 2.0 and is common to Apple’s traditional Cocoa patterns. An unsuccessful operation returns nil, a successful one returns a value.
func doSomething() -> String? { let success = (arc4random_uniform(2) == 1) // flip a coin if success {return "success"} // succeed return nil // fail } if let result = doSomething() { // use result here }
Starting in Swift 2.0, reserve this approach for initialization and prefer guard over if-let. Instead of using optionals as semaphores, that is to indicate success and fail conditions, use Swift’s new error handling system.
func betterDoSomething() throws -> String { let success = (arc4random_uniform(2) == 1) // flip a coin if success {return "success"} // succeed throw Error.failure // fail } do { let result = try betterDoSomething() } catch {print(error)}
This refactoring skips optionals; the nil-case is never of interest to the client code. Swift 2.0 error-handling means you never have to unwrap.
This one change profoundly affects Cocoa-sourced APIs. Calls that used NSError pointers pre-Swift 2.0 change their return type from optional to non-optional, add the throws keyword, and eliminate error pointers from API calls. The new system works sends NSError through do-try-catch.
// Old func dataFromRange(range: NSRange, documentAttributes dict: [NSObject : AnyObject], error: NSErrorPointer) -> NSData? // New func dataFromRange(range: NSRange, documentAttributes dict: [String : AnyObject]) throws -> NSData
By introducing error handling, optionals can eliminate their overloaded “failed call” semantics. It’s always better to use throws with well-defined errors than to use optional semaphores. When you really have no information to pass back to the caller other than “I failed”, Swift 2.0’s updated error system simplifies creating an error enumeration to explain why that failure occurred. It is ridiculously easy to add informative errors that don’t require complicated NSError initializers to construct.
enum Error: ErrorType {case BadData, MemoryGlitch, ItIsFriday}
Although many current APIs, especially those based on Core Foundation calls, have yet to transition to the new system, I encourage you to update your code to avoid using optionals as semaphores. Return your optionals to the “contains a value or does not contain a value” semantics they were designed to handle.
I’m working on the Swift Developer’s Cookbook for Pearson/Addison Wesley. Send me your questions and thanks for buying my books!
2 Comments
I totally get your rationale for taking a “throws” approach and I tried it extensively in my own code. However, it didn’t stick because it made my code ugly and I eventually reverted to using optional return values. Wherever I had been using “if let” (which I found very succinct and convenient) I was now forced to use “do … catch” (which seemed cumbersome and heavyweight.) I think that the deciding factor should be whether or not a method invocation can fail for multiple different reasons and whether your program can respond in kind. In those cases, as you suggest, use error enums and throws. If there’s only one reason for failure, or only one possible way to recover from it, my own experience (and code style) has taught that it’s simpler to use optionals. This approach also has the benefit of succinct optional chaining.
[…] great post by Erica Sadun on this subject, Nil vs […]