Lock in $30 Savings on PRO—Offer Ends Soon! ⏳

Swift 2 Error Handling vs Result<T, E>

Swift 2 Error Handling vs Result<T, E>

Swift 2 (& LLDB) シンポジウム
http://realm.connpass.com/event/16556/

Yasuhiro Inami

June 28, 2015
Tweet

More Decks by Yasuhiro Inami

Other Decks in Programming

Transcript

  1. Swift 2 Error Handling V.S. Result<T, E> 2015/06/27 Swift 2

    (& LLDB) Symposium Yasuhiro Inami / @inamiy
  2. Who? • Yasuhiro Inami / @inamiy • LINE Corp •

    WWDC 2015 Attendee • guard case .Left(let Him) = photo else {
 // It’s me
 throw stone 
 }
  3. 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
  4. 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
  5. 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 }];
  6. 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
  7. Swift 1 (Either/Result) • Optional<T> + Error • map, flatMap

    … functional • 3rd party: LlamaKit/LlamaKit → antitypical/Result enum Result<T, Error> { case Success(T) case Failure(Error) }
  8. Sync + Result func doSync<A, T, E>(arg: A) -> Result<T,

    E> ... let result = doSync(arg) switch result { case .Success(let value): // do success handling case .Failure(let error): // do error handling }
  9. Async + Result func doAsync<A, T, E>(arg: A, callback: (Result<T,

    E> -> Void)) ... doAsync(arg) { result in switch result { case .Success(let value): // do success handling case .Failure(let error): // do error handling } }
  10. 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)
  11. 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
  12. 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”
  13. 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 }
  14. 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...
  15. 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...
  16. 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 }
  17. 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
  18. Too loose ErrorType func throwLikeCrazy(x: Int) throws { switch x

    { case 0: throw MyError.XXX case 1: throw YourError.YYY default: throw HisError.ZZZ } }
  19. 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
  20. do { try throwLikeCrazy(x) } catch { // do error

    handling } Too loose ErrorType Catch… What???
  21. Async Issue func doSync<A, R>(arg: A) throws -> R Sync

    pattern func doAsync<A, R>(arg: A, callback: R -> Void) throws Async pattern?
  22. Async Issue func doSync<A, R>(arg: A) throws -> R Sync

    pattern func doAsync<A, R>(arg: A, callback: R -> Void) throws Async pattern? Can’t throw asynchronously
  23. func doSync<A, R>(arg: A) throws -> R func doSync<A, R>(arg:

    A)() throws -> R ↓ 2-step call for lazy evaluation
  24. Sync + Lazy Throwing let result = doSync(arg) do {

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

    let value = try result() // do success handling } catch { // do error handling } }
  26. Issues in Lazy Throwing • try result() is ugly •

    No `()`, please… • Ideally, it’s better if we can try Result<T, E> 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…)
  27. Issues in Result<T, E> • Result<T, E> 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
  28. 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(()); }
  29. 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(()) }
  30. func doSomething<T2, E2>() -> Result<T2, E2> { let result: Result<T1,

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

    E1> = ... 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! 㱻
  32. Recap • Both throwing function and Result<T, E> have pros

    and cons • “try result” is more important than ”try execution” • Rust’s Error Handling is awesome