Slide 1

Slide 1 text

Swift 2 Error Handling V.S. Result 2015/06/27 Swift 2 (& LLDB) Symposium Yasuhiro Inami / @inamiy

Slide 2

Slide 2 text

Who? • Yasuhiro Inami / @inamiy • LINE Corp • WWDC 2015 Attendee • guard case .Left(let Him) = photo else {
 // It’s me
 throw stone 
 }

Slide 3

Slide 3 text

Good old days…

Slide 4

Slide 4 text

Objective-C

Slide 5

Slide 5 text

Error Handling in Objective-C + (id)JSONObjectWithData:(NSData *)data options: (NSJSONReadingOptions)opt error:(NSError **)error NSData* jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding]; id json = [NSJSONSerialization JSONObjectWithData:jsonData options:1 error:NULL]; Method Usage

Slide 6

Slide 6 text

error:NULL];

Slide 7

Slide 7 text

Passing Error Pointer (Obj-C) NSError* error = nil; id json = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers error:&error]; if (error) { // do error handling return } // do success handling

Slide 8

Slide 8 text

Async Error Handling (Obj-C) NSURLSessionDataTask* task = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { if (error) { // do error handling return } // do success handling }];

Slide 9

Slide 9 text

or, Exception Handling (Obj-C) @try { // Code that can potentially throw an exception } @catch (NSException *exception) { // Handle an exception thrown in the @try block } @finally { // Code that gets executed // whether or not an exception is thrown } … but rarely used in Objective-C

Slide 10

Slide 10 text

Swift

Slide 11

Slide 11 text

Swift 1 (Either/Result) • Optional + Error • map, flatMap … functional • 3rd party: LlamaKit/LlamaKit → antitypical/Result enum Result { case Success(T) case Failure(Error) }

Slide 14

Slide 14 text

We were pretty happy with Result, until …

Slide 15

Slide 15 text

No content

Slide 16

Slide 16 text

Swift 2 Error Handling

Slide 17

Slide 17 text

do { try doSomething(arg) } catch { // do error handling }

Slide 18

Slide 18 text

do-try-catch

Slide 20

Slide 20 text

throws / rethrows

Slide 21

Slide 21 text

Java’s Exception Handling Model

Slide 22

Slide 22 text

No content

Slide 23

Slide 23 text

No content

Slide 24

Slide 24 text

Swift 2 Error Handling • Throwing Function • Returns value, or throws error (ErrorType) • Must declare throws / rethrows • Supertype of non-throwing function • Works swiftly with existing Objective-C Cocoa APIs • Can set breakpoint (e.g. br s -E swift -O MyError) • Similar to Java’s Checked Exception (not NSException)

Slide 25

Slide 25 text

Ref: Checked/Unchecked Exceptions • Checked Exception • Error from outside of program control & should be recoverable • Forces caller to use do-catch conditional blocks • No unwinding callstack penalties for unhandled errors • Unchecked Exception • Programmer’s mistake & should NOT be recoverable

Slide 28

Slide 28 text

do-try-catch / try! • do-try-catch, not “try-catch” • try expression is required for **every ** call of throwing function • or, try! (forced-try) to omit do-catch blocks • catch clause can be used like switch statement • Use defer in replace of “finally”

Slide 29

Slide 29 text

Comparison

Slide 30

Slide 30 text

Sequential flow (Result) let result3 = doSync1(arg) .flatMap(doSync2) .flatMap(doSync3) switch result3 { case .Success(let value3): // do success handling case .Failure(let error): // do error handling }

Slide 31

Slide 31 text

Sequential flow (Result) let result3 = doSync1(arg) .flatMap(doSync2) .flatMap(doSync3) switch result3 { case .Success(let value3): // do success handling case .Failure(let error): // do error handling } Functional All Error types must be same...

Slide 32

Slide 32 text

Sequential flow (Result) let result3 = doSync1(arg) .flatMap(doSync2.mapError {…}) .flatMap(doSync3.mapError {…}) switch result3 { case .Success(let value3): // do success handling case .Failure(let error): // do error handling } Error conversion is sometimes required...

Slide 33

Slide 33 text

Sequential flow (Throwing Func) do { let value1 = try doSync1(arg) let value2 = try doSync2(value1) let value3 = try doSync3(value2) // do success handling } catch { // do error handling }

Slide 34

Slide 34 text

Sequential flow (Throwing Func) do { let value1 = try doSync1(arg) let value2 = try doSync2(value1) let value3 = try doSync3(value2) // do success handling } catch { // do error handling } Imperative Can catch any ErrorTypes

Slide 35

Slide 35 text

Swift 2 Error Handling LGTM

Slide 36

Slide 36 text

Really?

Slide 37

Slide 37 text

Issues in Throwing Function • Too loose ErrorType • Async Issue • Separation of Concerns

Slide 38

Slide 38 text

Too loose ErrorType func throwLikeCrazy(x: Int) throws { switch x { case 0: throw MyError.XXX case 1: throw YourError.YYY default: throw HisError.ZZZ } }

Slide 39

Slide 39 text

func throwLikeCrazy(x: Int) throws { switch x { case 0: throw MyError.XXX case 1: throw YourError.YYY default: throw HisError.ZZZ } } Too loose ErrorType No ErrorType info Can throw many different ErrorTypes

Slide 40

Slide 40 text

do { try throwLikeCrazy(x) } catch { // do error handling } Too loose ErrorType Catch… What???

Slide 43

Slide 43 text

Throwing Function execution block + return value, or throw error =

Slide 44

Slide 44 text

all at once

Slide 45

Slide 45 text

Throwing Function only works synchronously

Slide 46

Slide 46 text

Workaround for asynchrony

Slide 47

Slide 47 text

Separation of Concerns

Slide 48

Slide 48 text

execution block + return value, or throw error

Slide 49

Slide 49 text

execution evaluation

Slide 50

Slide 50 text

Just like Result

Slide 54

Slide 54 text

Essentially... Void throws -> T Result 㱻

Slide 55

Slide 55 text

Sync + Lazy Throwing let result = doSync(arg) do { let value = try result() // do success handling } catch { // do error handling }

Slide 56

Slide 56 text

try result rather than try execution

Slide 57

Slide 57 text

Async + Lazy Throwing doAsync(arg) { result in do { let value = try result() // do success handling } catch { // do error handling } }

Slide 58

Slide 58 text

Lazy Throwing LGTM

Slide 59

Slide 59 text

Really?

Slide 60

Slide 60 text

Issues in Lazy Throwing • try result() is ugly • No `()`, please… • Ideally, it’s better if we can try Result enum rather than try throwing function • Too verbose for sync task & can’t throw immediately • Use lazy throwing for async only? 
 (but it will lack consistency…)

Slide 61

Slide 61 text

… Shouldn’t we just go back to Result?

Slide 62

Slide 62 text

Result LGTM I changed my mind

Slide 63

Slide 63 text

Really?

Slide 64

Slide 64 text

Issues in Result • Result is just an Enum • Same weight for both success value and error • No compiler support (breakpoint, do-catch) • Too easy to ignore errors • var value: T? … Result → Optional conversion • map() / flatMap() … using success value without checking error

Slide 65

Slide 65 text

SO… WHAT IS THE BEST SOLUTION?

Slide 66

Slide 66 text

Rust

Slide 67

Slide 67 text

Error Handling in Rust // before fn write_info(info: &Info) -> io::Result<()> { let mut file = File::create("list.txt").unwrap(); if let Err(e) = writeln!(&mut file, "name: {}", info.name) { return Err(e) } if let Err(e) = writeln!(&mut file, "age: {}", info.age) { return Err(e) } return Ok(()); }

Slide 68

Slide 68 text

Error Handling in Rust // after fn write_info(info: &Info) -> io::Result<()> { let mut file = try!(File::create("list.txt")); // Early return on error try!(writeln!(&mut file, "name: {}", info.name)); try!(writeln!(&mut file, "age: {}", info.age)); Ok(()) }

Slide 69

Slide 69 text

If rewrite in Swift…

Slide 70

Slide 70 text

func doSomething() -> Result { let result: Result = ... let value = try result

Slide 71

Slide 71 text

func doSomething() -> Result { let result: Result = ... let value = try result let value: T1 switch result { case .Success(let v1): value = v1 case .Failure(let e1): return .Failure(E2.convertFrom(e1)) } 㱻

Slide 72

Slide 72 text

func doSomething() -> Result { let result: Result = ... let value = try result let value: T1 switch result { case .Success(let v1): value = v1 case .Failure(let e1): return .Failure(E2.convertFrom(e1)) } Early-exit with auto-mapping Error! 㱻

Slide 73

Slide 73 text

Rust uses Result + try result

Slide 74

Slide 74 text

Much cleverer & type safe than throw

Slide 75

Slide 75 text

Recap

Slide 76

Slide 76 text

Recap • Both throwing function and Result have pros and cons • “try result” is more important than ”try execution” • Rust’s Error Handling is awesome

Slide 77

Slide 77 text

Q. Will we be able to ”try result” in future Swift?

Slide 78

Slide 78 text

Let’s dream!

Slide 79

Slide 79 text

Thank you!