Rationale for `throws` vs Result<T,E>?

When Swift 2 was announced, I filed a radar (rdar://problem/21305056) suggesting that Swift should drop the `throws` annotation in favor of a stdlib-provided Result<T,E> type, but otherwise keep all the sugar (e.g. try and do {} catch {}). This change would lose the ability to mark a function as `rethrows` but would gain more explicit error typing as well as a consistent story for error handling between synchronous and asynchronous methods. Although FWIW, explicit error typing could be added to the `throws` keyword and Result<T,E> could still be used for asynchronous error handling, it just wouldn't be consistent with synchronous error handling. And even though I filed that radar, I'm not actually 100% convinced it's the better approach anyway, I just wanted it to be considered. Although I do think Swift 2 should provide an official Result<T,E> type and use it for asynchronous errors since `throws` doesn't work there.


Anyway, the radar was closed with a comment "Thank you for the feedback. We discussed this extensively. If you’d like some rationale, please ask on the developer forums.". To that end, I would appreciate it if any Apple employee could chime in with an explanation of the rationale.

But the Result type would have to be magical if we want to force try, catch, and throws, wouldn't it?

“Although I do think Swift 2 should provide an official Result<T,E> type and use it for asynchronous errors since `throws` doesn't work there.”


Not sure if you've considered this, but it's currently possible use throws for asynchronous errors by supplying a throwing closure as the argument to the callback function.

Wouldn't that be the other way around? I thought Eridius meant that the error should be reported to the closure.

I probably haven't explained it clearly, since I think we're agreeing. Say you have some asynchronous call that produces an integer result.

func asynchronous(callback: Int -> ()) {
  // pretend this is asynchronous
  callback(0)
}

asynchronous { result in print(result) }

If you want to add error handling, you could either change the parameter to the callback from Int to Result<Int, Error> as suggested above, or you could just pass a throwing closure to the callback

func asynchronous(callback: ( () throws -> Int ) -> ()) {
  callback { 0 }
  // or, on some other path, callback { throw Error }
}

asynchronous { result in
  do {
    let i = try result()
    print(i)
  } catch {
    // handle errors
  }
}

forcing you to handle the error in the callback closure if you want to work with the result there.

Ah, yes, I wasn't thinking backwards enough. 🙂


So, the remaining question is: would it make sense to encode this pattern into the language, just as the throws pattern has been? Right now, throws means "an extra return value", could we have another mechanism for "an extra parameter"? Or, if we want to speak Objective-C: a pattern for NSError *, and not just NSError **.


Or maybe just "a closure that returns value or throws" that looks like an argument. Sort of the opposite to @autoclosure.

I suppose you could do that, but that seems kind of awkward, since now you have to call a function to get your result.

I'm thinking something along these lines:


func asynchronous(input: String, callback: ( Int ) acceptsthrow -> ()) {
    guard isvalid(input) else {
        throwin callback(NSError(domain: "test", code: 4711, userInfo: nil))
        return
    }
    callback(0)
}


asynchronous("something") { result in
    print(result)
} catch {
    print(error)
}
Accepted Answer

Hi Eridius,


The general concern about Result types is that they are too easy to ignore, and in the space of error handling we wanted the error of omission (i.e., forgetting to think about error handling) to result in a build error.


There is nothing in our model that prevents a Result type from being used, and in fact, it would be very natural to use the Swift 2 error handling model along with a Result type for async and other functions that want to propagate "result|error" values across threads or other boundaries. What we need is a function to transform an function that throws (which would typically be a closure expr in practice) into a Result, and turn a Result into an value or a thrown error.


-Chris

Hi Chris,


Thanks for the response, and I apologize for taking so long to read it.


The concern you raise is very valid, but it is solvable in the Result model. Rust has what I think is a rather elegant solution here, which is a type-level attribute

#[must_use]
. This is similar to Swift 2.0's
@warn_unused_result
but it warns you whenever a function returns a value of the modified type and the value is not handled by the user code. This means that every function that returns a
Result<T,E>
must have the result handled or it will emit a warning.


I do agree that a Result type can coexist with Swift's current error model, and I really hope the Swift standard library grows a

Result<T,E>
type specifically to handle this (both so the Cocoa frameworks can use this for async error handling, and so we don't end up with a dozen different incompatible implementations that make interoperation between libraries very difficult). But beyond that, I wish Swift's error model would let me declare what type of error I can throw, both for documentation purposes and so the compiler can guarantee the caller has handled all errors without needing a catch-all
default
clause. And with that, some way of declaring an alias for a collection of error types, so you can have a bunch of functions that throw the same several error types without repeating yourself everywhere. And some syntax to make going between Result and throwable functions easier (perhaps
try? func()
to get a Result, although the use of
?
does initially suggest that it returns an Optional, although doing so would throw away the error info and that's no good).
Rationale for &#96;throws&#96; vs Result&lt;T,E&gt;?
 
 
Q