> On 17 Feb 2017, at 19:45, Anton Zhilin via swift-evolution 
> <[email protected]> wrote:
> 
> Now this is on-topic, I guess.
> Last time we stopped at John McCall’s syntax:
> 
> extension MyError: Error { ... }
> 
> func foo() throws(MyError) -> MyResult
> It’s conservative and prevents visual ambiguity with extra parentheses.
> 
> If we (somewhat) agree on this, then submitting a proposal will be trivial.
> 
> _______________________________________________
> swift-evolution mailing list
> [email protected]
> https://lists.swift.org/mailman/listinfo/swift-evolution


So, I’m not sure about what was decided last time, but my issues with this are:

- The thrown error type will become part of the ABI of the function. If you 
change the type of Error that is thrown, callers may not catch it. At the same 
time, if we make enums resilient by default and only allow specifying a single 
entire type, you will basically need one Error enum per function and it will 
need to be @fixed if you actually want to remove the catch-all block. Otherwise:

// Let’s say this isn’t @fixed...
enum CanFailError {
    errorOne
    errorTwo
}

func canFail() throws(CanFailError) { /* … */ }

do { try canFail() }
catch CanFailError {
    switch error {
        case .errorOne: /* handle error one */
        case .errorTwo: /* handle error two */
        default:        /* handle possible new errors in later versions of the 
library */
    }
}

do { try canFail() }
catch .errorOne { /* handle error one */ }
catch .errorTwo { /* handle error two */  }
catch           { /* handle possible new errors in later versions of the 
library */ }

- I usually have _semantic_ namespaces for Errors, rather than single types per 
implementation pattern. If we are adding strong annotations about which errors 
can be thrown, I’d quite like to incorporate that pattern. For example:

extension File {
@fixed enum OpeningError { 
        case .invalidPath
        case .accessDenied  // e.g. asking for write permissions for read-only 
file
}
@fixed enum ReadError {
        case .invalidOffset // past EOF
        case .deviceError   // probably worth aborting the entire operation the 
read is part of
}

// - throws:
//     - .OpeningError if the file can’t be opened
//     - .ReadError if the read operation fails
func read(from offset: Int, into buffer: UnsafeBufferPointer<UInt8>) 
throws(OpeningError, ReadError) { /* … */ }
}

- I wonder if we could try something more ambitious. Since the list of thrown 
errors is resilience-breaking for the function, it is only beneficial for 
versioned and @inlineable functions. They should not be able to add new errors 
(they can remove them though, since errors are intended to be switched over). I 
wonder if we couldn’t introduce a small pattern grammar for our structured 
comments (isolated from the rest of the language) - it would be optional, but 
if you do list your errors, the compiler would validate that you do it 
exhaustively. Some patterns I would like are:

// - throws: - MyError.{errorOne, errorThree, errorFive}: Something bad      || 
considered exhaustive
@inlineable public func canFail() throws {}

// - throws: - OpeningError: Computer says nooooo...     || considered 
exhaustive if OpeningError is versioned or @fixed
//           - *                                         || other errors, 
requires “catch-all” by external callers
@inlineable public func canFail2() throws {}

If we want to get really clever, we can have the compiler automatically 
generate those error-lists for internal functions, so you would automatically 
get exhaustive error-handling within your own module.

- Karl
_______________________________________________
swift-evolution mailing list
[email protected]
https://lists.swift.org/mailman/listinfo/swift-evolution

Reply via email to