| Error Handling in Swift 2.0 |
| =========================== |
| |
| As a tentpole feature for Swift 2.0, we are introducing a new |
| first-class error handling model. This feature provides standardized |
| syntax and language affordances for throwing, propagating, catching, |
| and manipulating recoverable error conditions. |
| |
| Error handling is a well-trod path, with many different approaches in |
| other languages, many of them problematic in various ways. We believe |
| that our approach provides an elegant solution, drawing on the lessons |
| we've learned from other languages and fixing or avoiding some of the |
| pitfalls. The result is expressive and concise while still feeling |
| explicit, safe, and familiar; and we believe it will work beautifully |
| with the Cocoa APIs. |
| |
| We're intentionally not using the term "exception handling", which |
| carries a lot of connotations from its use in other languages. Our |
| proposal has some similarities to the exceptions systems in those |
| languages, but it also has a lot of important differences. |
| |
| Kinds of Error |
| -------------- |
| |
| What exactly is an "error"? There are many possible error conditions, |
| and they don't all make sense to handle in exactly the same way, |
| because they arise in different circumstances and programmers have to |
| react to them differently. |
| |
| We can break errors down into four categories, in increasing order of |
| severity: |
| |
| A **simple domain error** arises from an operation that can fail in |
| some obvious way and which is often invoked speculatively. Parsing an |
| integer from a string is a really good example. The client doesn't |
| need a detailed description of the error and will usually want to |
| handle the error immediately. These errors are already well-modeled |
| by returning an optional value; we don't need a more complex language |
| solution for them. |
| |
| A **recoverable error** arises from an operation which can fail in |
| complex ways, but whose errors can be reasonably anticipated in |
| advance. Examples including opening a file or reading from a network |
| connection. These are the kinds of errors that Apple's APIs use |
| NSError for today, but there are close analogues in many other APIs, |
| such as ``errno`` in POSIX. |
| |
| Ignoring this kind of error is usually a bad idea, and it can even be |
| dangerous (e.g. by introducing a security hole). Developers should be |
| strongly encouraged to write code that handles the error. It's common |
| for developers to want to handle errors from different operations in |
| the same basic way, either by reporting the error to the user or |
| passing the error back to their own clients. |
| |
| These errors will be the focus of this proposal. |
| |
| The final two classes of error are outside the scope of this proposal. |
| A **universal error** is theoretically recoverable, but by its nature |
| the language can't help the programmer anticipate where it will come |
| from. A **logic failure** arises from a programmer mistake and should |
| not be recoverable at all. In our system, these kinds of errors are |
| reported either with Objective-C/C++ exceptions or simply by |
| logging a message and calling ``abort()``. Both kinds of error are |
| discussed extensively in the rationale. Having considered them |
| carefully, we believe that we can address them in a later release |
| without significant harm. |
| |
| Aspects of the Design |
| --------------------- |
| |
| This approach proposed here is very similar to the error handling |
| model manually implemented in Objective-C with the ``NSError`` |
| convention. Notably, the approach preserves these advantages of this |
| convention: |
| |
| - Whether a method produces an error (or not) is an explicit part of |
| its API contract. |
| |
| - Methods default to *not* producing errors unless they are explicitly |
| marked. |
| |
| - The control flow within a function is still mostly explicit: a |
| maintainer can tell exactly which statements can produce an error, |
| and a simple inspection reveals how the function reacts to the |
| error. |
| |
| - Throwing an error provides similar performance to allocating an |
| error and returning it -- it isn't an expensive, table-based stack |
| unwinding process. |
| |
| - Cocoa APIs using standard ``NSError`` patterns can be imported into |
| this world automatically. Other common patterns (e.g. ``CFError``, |
| ``errno``) can be added to the model in future versions of Swift. |
| |
| In addition, we feel that this design improves on Objective-C's error |
| handling approach in a number of ways: |
| |
| - It eliminates a lot of boilerplate control-flow code for propagating |
| errors. |
| |
| - The syntax for error handling will feel familiar to people used to |
| exception handling in other languages. |
| |
| - Defining custom error types is simple and ties in elegantly with |
| Swift enums. |
| |
| As to basic syntax, we decided to stick with the familiar language of |
| exception handling. We considered intentionally using different terms |
| (like ``raise`` / ``handle``) to try to distinguish our approach from |
| other languages. However, by and large, error propagation in this |
| proposal works like it does in exception handling, and people are |
| inevitably going to make the connection. Given that, we couldn't find |
| a compelling reason to deviate from the ``throw`` / ``catch`` legacy. |
| |
| This document just contains the basic proposal and will be very |
| light on rationale. We considered many different languages and |
| programming environments as part of making this proposal, and there's |
| an extensive discussion of them in the separate rationale document. |
| For example, that document explains why we don't simply allow all |
| functions to throw, why we don't propagate errors using simply an |
| ``ErrorOr<T>`` return type, and why we don't just make error propagation |
| part of a general monad feature. We encourage you to read that |
| rationale if you're interested in understanding why we made the |
| decisions we did. |
| |
| With that out of the way, let's get to the details of the proposal. |
| |
| Typed propagation |
| ----------------- |
| |
| Whether a function can throw is part of its type. This applies to all |
| functions, whether they're global functions, methods, or closures. |
| |
| By default, a function cannot throw. The compiler statically enforces |
| this: anything the function does which can throw must appear in a |
| context which handles all errors. |
| |
| A function can be declared to throw by writing ``throws`` on the |
| function declaration or type:: |
| |
| func foo() -> Int { // This function is not permitted to throw. |
| func bar() throws -> Int { // This function is permitted to throw. |
| |
| ``throws`` is written before the arrow to give a sensible and consistent |
| grammar for function types and implicit ``()`` result types, e.g.:: |
| |
| func baz() throws { |
| |
| // Takes a 'callback' function that can throw. |
| // 'fred' itself can also throw. |
| func fred(_ callback: (UInt8) throws -> ()) throws { |
| |
| // These are distinct types. |
| let a : () -> () -> () |
| let b : () throws -> () -> () |
| let c : () -> () throws -> () |
| let d : () throws -> () throws -> () |
| |
| For curried functions, ``throws`` only applies to the innermost |
| function. This function has type ``(Int) -> (Int) throws -> Int``:: |
| |
| func jerry(_ i: Int)(j: Int) throws -> Int { |
| |
| ``throws`` is tracked as part of the type system: a function value |
| must also declare whether it can throw. Functions that cannot throw |
| are a subtype of functions that can, so you can use a function that |
| can't throw anywhere you could use a function that can:: |
| |
| func rachel() -> Int { return 12 } |
| func donna(_ generator: () throws -> Int) -> Int { ... } |
| |
| donna(rachel) |
| |
| The reverse is not true, since the caller would not be prepared to |
| handle the error. |
| |
| A call to a function which can throw within a context that is not |
| allowed to throw is rejected by the compiler. |
| |
| It isn't possible to overload functions solely based on whether the |
| functions throw. That is, this is not legal:: |
| |
| func foo() { |
| func foo() throws { |
| |
| A throwing method cannot override a non-throwing method or satisfy a |
| non-throwing protocol requirement. However, a non-throwing method can |
| override a throwing method or satisfy a throwing protocol requirement. |
| |
| It is valuable to be able to overload higher-order functions based on |
| whether an argument function throws, so this is allowed:: |
| |
| func foo(_ callback: () throws -> Bool) { |
| func foo(_ callback: () -> Bool) { |
| |
| ``rethrows`` |
| ~~~~~~~~~~~~ |
| |
| Functions which take a throwing function argument (including as an |
| autoclosure) can be marked as ``rethrows``:: |
| |
| extension Array { |
| func map<U>(_ fn: ElementType throws -> U) rethrows -> [U] |
| } |
| |
| It is an error if a function declared ``rethrows`` does not include a |
| throwing function in at least one of its parameter clauses. |
| |
| ``rethrows`` is identical to ``throws``, except that the function |
| promises to only throw if one of its argument functions throws. |
| |
| More formally, a function is *rethrowing-only* for a function *f* if: |
| |
| - it is a throwing function parameter of *f*, |
| |
| - it is a non-throwing function, or |
| |
| - it is implemented within *f* (i.e. it is either *f* or a function or |
| closure defined therein) and it does not throw except by either: |
| |
| - calling a function that is rethrowing-only for *f* or |
| |
| - calling a function that is ``rethrows``, passing only functions |
| that are rethrowing-only for *f*. |
| |
| It is an error if a ``rethrows`` function is not rethrowing-only for |
| itself. |
| |
| A ``rethrows`` function is considered to be a throwing function. |
| However, a direct call to a ``rethrows`` function is considered to not |
| throw if it is fully applied and none of the function arguments can |
| throw. For example:: |
| |
| // This call to map is considered not to throw because its |
| // argument function does not throw. |
| let absolutePaths = paths.map { "/" + $0 } |
| |
| // This call to map is considered to throw because its |
| // argument function does throw. |
| let streams = try absolutePaths.map { try InputStream(filename: $0) } |
| |
| For now, ``rethrows`` is a property of declared functions, not of |
| function values. Binding a variable (even a constant) to a function |
| loses the information that the function was ``rethrows``, and calls to |
| it will use the normal rules, meaning that they will be considered to |
| throw regardless of whether a non-throwing function is passed. |
| |
| For the purposes of override and conformance checking, ``rethrows`` |
| lies between ``throws`` and non-``throws``. That is, an ordinary |
| throwing method cannot override a ``rethrows`` method, which cannot |
| override a non-throwing method; but an ordinary throwing method can be |
| overridden by a ``rethrows`` method, which can be overridden by a |
| non-throwing method. Equivalent rules apply for protocol conformance. |
| |
| Throwing an error |
| ----------------- |
| |
| The ``throw`` statement begins the propagation of an error. It always |
| takes an argument, which can be any value that conforms to the |
| ``Error`` protocol (described below). |
| |
| :: |
| |
| if timeElapsed > timeThreshold { |
| throw HomeworkError.Overworked |
| } |
| |
| throw NSError(domain: "whatever", code: 42, userInfo: nil) |
| |
| As mentioned above, attempting to throw an error out of a function not |
| marked ``throws`` is a static compiler error. |
| |
| Catching errors |
| --------------- |
| |
| A ``catch`` clause includes an optional pattern that matches the |
| error. This pattern can use any of the standard pattern-matching |
| tools provided by ``switch`` statements in Swift, including boolean |
| ``where`` conditions. The pattern can be omitted; if so, a ``where`` |
| condition is still permitted. If the pattern is omitted, or if it |
| does not bind a different name to the error, the name ``error`` is |
| automatically bound to the error as if with a ``let`` pattern. |
| |
| The ``try`` keyword is used for other purposes which it seems to fit far |
| better (see below), so ``catch`` clauses are instead attached to a |
| generalized ``do`` statement:: |
| |
| // Simple do statement (without a trailing while condition), |
| // just provides a scope for variables defined inside of it. |
| do { |
| let x = foo() |
| } |
| |
| // do statement with two catch clauses. |
| do { |
| ... |
| |
| } catch HomeworkError.Overworked { |
| // a conditionally-executed catch clause |
| |
| } catch _ { |
| // a catch-all clause. |
| } |
| |
| As with ``switch`` statements, Swift makes an effort to understand |
| whether catch clauses are exhaustive. If it can determine it is, then |
| the compiler considers the error to be handled. If not, the error |
| automatically propagates out of scope, either to a lexically |
| enclosing ``catch`` clause or out of the containing function (which must |
| be marked ``throws``). |
| |
| We expect to refine the ``catch`` syntax with usage experience. |
| |
| ``Error`` |
| ----------------- |
| |
| The Swift standard library will provide ``Error``, a protocol with |
| a very small interface (which is not described in this proposal). The |
| standard pattern should be to define the conformance of an ``enum`` to |
| the type:: |
| |
| enum HomeworkError : Error { |
| case Overworked |
| case Impossible |
| case EatenByCat(Cat) |
| case StopStressingMeWithYourRules |
| } |
| |
| The ``enum`` provides a namespace of errors, a list of possible errors |
| within that namespace, and optional values to attach to each option. |
| |
| Note that this corresponds very cleanly to the ``NSError`` model of an |
| error domain, an error code, and optional user data. We expect to |
| import system error domains as enums that follow this approach and |
| implement ``Error``. ``NSError`` and ``CFError`` themselves will also |
| conform to ``Error``. |
| |
| The physical representation (still being nailed down) will make it |
| efficient to embed an ``NSError`` as an ``Error`` and vice-versa. It |
| should be possible to turn an arbitrary Swift ``enum`` that conforms to |
| ``Error`` into an ``NSError`` by using the qualified type name as the |
| domain key, the enumerator as the error code, and turning the payload |
| into user data. |
| |
| Automatic, marked, propagation of errors |
| ---------------------------------------- |
| |
| Once an error is thrown, Swift will automatically propagate it out of |
| scopes (that permit it), rather than relying on the programmer to |
| manually check for errors and do their own control flow. This is just |
| a lot less boilerplate for common error handling tasks. However, |
| doing this naively would introduce a lot of implicit control flow, |
| which makes it difficult to reason about the function's behavior. |
| This is a serious maintenance problem and has traditionally been a |
| considerable source of bugs in languages that heavily use exceptions. |
| |
| Therefore, while Swift automatically propagates errors, it requires |
| that statements and expressions that can implicitly throw be marked |
| with the ``try`` keyword. For example:: |
| |
| func readStuff() throws { |
| // loadFile can throw an error. If so, it propagates out of readStuff. |
| try loadFile("mystuff.txt") |
| |
| // This is a semantic error; the 'try' keyword is required |
| // to indicate that it can throw. |
| var y = stream.readFloat() |
| |
| // This is okay; the try covers the entire statement. |
| try y += stream.readFloat() |
| |
| // This try applies to readBool(). |
| if try stream.readBool() { |
| // This try applies to both of these calls. |
| let x = try stream.readInt() + stream.readInt() |
| } |
| |
| if let err = stream.getOutOfBandError() { |
| // Of course, the programmer doesn't have to mark explicit throws. |
| throw err |
| } |
| } |
| |
| Developers can choose to "scope" the ``try`` very tightly by writing it |
| within parentheses or on a specific argument or list element:: |
| |
| // Ok. |
| let x = (try stream.readInt()) + (try stream.readInt()) |
| |
| // Semantic error: the try only covers the parenthesized expression. |
| let x2 = (try stream.readInt()) + stream.readInt() |
| |
| // The try applies to the first array element. Of course, the |
| // developer could cover the entire array by writing the try outside. |
| let array = [ try foo(), bar(), baz() ] |
| |
| Some developers may wish to do this to make the specific throwing |
| calls very clear. Other developers may be content with knowing that |
| something within a statement can throw. The compiler's fixit hints will |
| guide developers towards inserting a single ``try`` that covers the entire |
| statement. This could potentially be controlled someday by a coding |
| style flag passed to the compiler. |
| |
| ``try!`` |
| ~~~~~~~~ |
| |
| To concisely indicate that a call is known to not actually throw at |
| runtime, ``try`` can be decorated with ``!``, turning the error check |
| into a runtime assertion that the call does not throw. |
| |
| For the purposes of checking that all errors are handled, a ``try!`` |
| expression is considered to handle any error originating from within |
| its operand. |
| |
| ``try!`` is otherwise exactly like ``try``: it can appear in exactly |
| the same positions and doesn't affect the type of an expression. |
| |
| Manual propagation and manipulation of errors |
| --------------------------------------------- |
| |
| Taking control over the propagation of errors is important for some |
| advanced use cases (e.g. transporting an error result across threads |
| when synchronizing a future) and can be more convenient or natural for |
| specific use cases (e.g. handling a specific call differently within a |
| context that otherwise allows propagation). |
| |
| As such, the Swift standard library should provide a standard |
| Rust-like ``Result<T>`` enum, along with API for working with it, |
| e.g.: |
| |
| - A function to evaluate an error-producing closure and capture the |
| result as a ``Result<T>``. |
| |
| - A function to unpack a ``Result<T>`` by either returning its |
| value or propagating the error in the current context. |
| |
| This is something that composes on top of the basic model, but that |
| has not been designed yet and details aren't included in this |
| proposal. |
| |
| The name ``Result<T>`` is a stand-in and needs to be designed and |
| reviewed, as well as the basic operations on the type. |
| |
| ``defer`` |
| --------- |
| |
| Swift should provide a ``defer`` statement that sets up an *ad hoc* |
| clean-up action to be run when the current scope is exited. This |
| replicates the functionality of a Java-style ``finally``, but more |
| cleanly and with less nesting. |
| |
| This is an important tool for ensuring that explicitly-managed |
| resources are released on all paths. Examples include closing a |
| network connection and freeing memory that was manually allocated. It |
| is convenient for all kinds of error-handling, even manual propagation |
| and simple domain errors, but is especially nice with automatic |
| propagation. It is also a crucial part of our long-term vision for |
| universal errors. |
| |
| ``defer`` may be followed by an arbitrary statement. The compiler |
| should reject a ``defer`` action that might terminate early, whether by |
| throwing or with ``return``, ``break``, or ``continue``. |
| |
| Example:: |
| |
| if exists(filename) { |
| let file = open(filename, O_READ) |
| defer close(file) |
| |
| while let line = try file.readline() { |
| ... |
| } |
| |
| // close occurs here, at the end of the formal scope. |
| } |
| |
| If there are multiple defer statements in a scope, they are guaranteed |
| to be executed in reverse order of appearance. That is:: |
| |
| let file1 = open("hello.txt") |
| defer close(file1) |
| let file2 = open("world.txt") |
| defer close(file2) |
| ... |
| // file2 will be closed first. |
| |
| A potential extension is to provide a convenient way to mark that a |
| defer action should only be taken if an error is thrown. This is a |
| convenient shorthand for controlling the action with a flag. We will |
| evaluate whether adding complexity to handle this case is justified |
| based on real-world usage experience. |
| |
| Importing Cocoa |
| --------------- |
| |
| If possible, Swift's error-handling model should transparently work |
| with the SDK with a minimal amount of effort from framework owners. |
| |
| We believe that we can cover the vast majority of Objective-C APIs |
| with ``NSError**`` out-parameters by importing them as ``throws`` and |
| removing the error clause from their signature. That is, a method |
| like this one from ``NSAttributedString``:: |
| |
| - (NSData *)dataFromRange:(NSRange)range |
| documentAttributes:(NSDictionary *)dict |
| error:(NSError **)error; |
| |
| would be imported as:: |
| |
| func dataFromRange(_ range: NSRange, |
| documentAttributes dict: NSDictionary) throws -> NSData |
| |
| There are a number of cases to consider, but we expect that most can |
| be automatically imported without extra annotation in the SDK, by |
| using a couple of simple heuristics: |
| |
| * The most common pattern is a ``BOOL`` result, where a false value |
| means an error occurred. This seems unambiguous. |
| |
| * Also common is a pointer result, where a ``nil`` result usually |
| means an error occurred. This appears to be universal in |
| Objective-C; APIs that can return ``nil`` results seem to do so via |
| out-parameters. So it seems to be safe to make a policy decision |
| that it's okay to assume that a ``nil`` result is an error by |
| default. |
| |
| If the pattern for a method is that a ``nil`` result means it produced |
| an error, then the result can be imported as a non-optional type. |
| |
| * A few APIs return ``void``. As far as I can tell, for all of these, |
| the caller is expected to check for a non-``nil`` error. |
| |
| For other sentinel cases, we can consider adding a new clang attribute |
| to indicate to the compiler what the sentinel is: |
| |
| * There are several APIs returning ``NSInteger`` or ``NSUInteger``. At |
| least some of these return 0 on error, but that doesn't seem like a |
| reasonable general assumption. |
| |
| * ``AVFoundation`` provides a couple methods returning |
| ``AVKeyValueStatus``. These produce an error if the API returned |
| ``AVKeyValueStatusFailed``, which, interestingly enough, is not the |
| zero value. |
| |
| The clang attribute would specify how to test the return value for an |
| error. For example:: |
| |
| + (NSInteger)writePropertyList:(id)plist |
| toStream:(NSOutputStream *)stream |
| format:(NSPropertyListFormat)format |
| options:(NSPropertyListWriteOptions)opt |
| error:(out NSError **)error |
| NS_ERROR_RESULT(0); |
| |
| - (AVKeyValueStatus)statusOfValueForKey:(NSString *)key |
| error:(NSError **) |
| NS_ERROR_RESULT(AVKeyValueStatusFailed); |
| |
| We should also provide a Clang attribute which specifies that the |
| correct way to test for an error is to check the out-parameter. Both |
| of these attributes could potentially be used by the static analyzer, |
| not just Swift. (For example, they could try to detect an invalid |
| error check.) |
| |
| Cases that do not match the automatically imported patterns and that |
| lack an attribute would be left unmodified (i.e., they'd keep their |
| NSErrorPointer argument) and considered "not awesome" in the SDK |
| auditing tool. These will still be usable in Swift: callers will get |
| the NSError back like they do today, and have to throw the result |
| manually. |
| |
| For initializers, importing an initializer as throwing takes |
| precedence over importing it as failable. That is, an imported |
| initializer with a nullable result and an error parameter would be |
| imported as throwing. Throwing initializers have very similar |
| constraints to failable initializers; in a way, it's just a new axis |
| of failability. |
| |
| One limitation of this approach is that we need to be able to reconstruct |
| the selector to use when an overload of a method is introduced. For this |
| reason, the import is likely to be limited to methods where the error |
| parameter is the last one and the corresponding selector |
| chunk is either ``error:`` or the first chunk (see below). Empirically, |
| this seems to do the right thing for all but two sets of APIs in the |
| public API: |
| |
| * The ``ISyncSessionDriverDelegate`` category on ``NSObject`` declares |
| half-a-dozen methods like this:: |
| |
| - (BOOL)sessionDriver:(ISyncSessionDriver *)sender |
| didRegisterClientAndReturnError:(NSError **)outError; |
| |
| Fortunately, these delegate methods were all deprecated in Lion, and |
| are thus unavailable in Swift. |
| |
| * ``NSFileCoordinator`` has half a dozen methods where the ``error:`` |
| clause is second-to-last, followed by a block argument. These |
| methods are not deprecated as far as I know. |
| |
| The above translation rule would import methods like this one from |
| ``NSDocument``:: |
| |
| - (NSDocument *)duplicateAndReturnError:(NSError **)outError; |
| |
| like so:: |
| |
| func duplicateAndReturnError() throws -> NSDocument |
| |
| The ``AndReturnError`` bit is common but far from universal; consider |
| this method from ``NSManagedObject``:: |
| |
| - (BOOL)validateForDelete:(NSError **)error; |
| |
| This would be imported as:: |
| |
| func validateForDelete() throws |
| |
| This is a really nice import, and it's somewhat unfortunate that we |
| can't import ``duplicateAndReturnError:`` as ``duplicate()``. |
| |
| |
| Potential future extensions to this model |
| ----------------------------------------- |
| |
| We believe that the proposal above is sufficient to provide a huge |
| step forward in error handling in Swift programs, but there is always |
| more to consider in the future. Some specific things we've discussed |
| (and may come back to in the future) but don't consider to be core to |
| the Swift 2.0 model are: |
| |
| Higher-order polymorphism |
| ~~~~~~~~~~~~~~~~~~~~~~~~~ |
| |
| We should make it easy to write higher-order functions that behave |
| polymorphically with respect to whether their arguments throw. This |
| can be done in a fairly simple way: a function can declare that it |
| throws if any of a set of named arguments do. As an example (using |
| strawman syntax):: |
| |
| func map<T, U>(_ array: [T], fn: T -> U) throwsIf(fn) -> [U] { |
| ... |
| } |
| |
| There's no need for a more complex logical operator than disjunction |
| for normal higher-order stuff. |
| |
| This feature is highly desired (e.g. it would allow many otherwise |
| redundant overloads to be collapsed into a single definition), but it |
| may or may not make it into Swift 2.0 based on schedule limitations. |
| |
| Generic polymorphism |
| ~~~~~~~~~~~~~~~~~~~~ |
| |
| For similar reasons to higher-order polymorphism, we should consider |
| making it easier to parameterize protocols on whether their operations |
| can throw. This would allow the writing of generic algorithms, e.g. |
| over ``Sequence``, that handle both conformances that cannot throw (like |
| ``Array``) and those that can (like a hypothetical cloud-backed |
| implementation). |
| |
| However, this would be a very complex feature, yet to be designed, and |
| it is far out-of-scope for Swift 2.0. In the meantime, most standard |
| protocols will be written to not allow throwing conformances, so as to |
| not burden the use of common generic algorithms with spurious |
| error-handling code. |
| |
| Statement-like functions |
| ~~~~~~~~~~~~~~~~~~~~~~~~ |
| |
| Some functions are designed to take trailing closures that feel like |
| sub-statements. For example, ``autoreleasepool`` can be used this way:: |
| |
| autoreleasepool { |
| foo() |
| } |
| |
| The error-handling model doesn't cause major problems for this. The |
| compiler can infer that the closure throws, and ``autoreleasepool`` |
| can be overloaded on whether its argument closure throws; the |
| overload that takes a throwing closures would itself throw. |
| |
| There is one minor usability problem here, though. If the closure |
| contains throwing expressions, those expressions must be explicitly |
| marked within the closure with ``try``. However, from the compiler's |
| perspective, the call to ``autoreleasepool`` is also a call that |
| can throw, and so it must also be marked with ``try``:: |
| |
| try autoreleasepool { // 'try' is required here... |
| let string = try parseString() // ...and here. |
| ... |
| } |
| |
| This marking feels redundant. We want functions like |
| ``autoreleasepool`` to feel like statements, but marks inside built-in |
| statements like ``if`` don't require the outer statement to be marked. |
| It would be better if the compiler didn't require the outer ``try``. |
| |
| On the other hand, the "statement-like" story already has a number of |
| other holes: for example, ``break``, ``continue``, and ``return`` |
| behave differently in the argument closure than in statements. In the |
| future, we may consider fixing that; that fix will also need to |
| address the error-propagation problem. |
| |
| ``using`` |
| ~~~~~~~~~ |
| |
| A ``using`` statement would acquire a resource, holds it for a fixed |
| period of time, optionally binds it to a name, and then releases it |
| whenever the controlled statement exits. ``using`` has many |
| similarities to ``defer``. It does not subsume ``defer``, which is useful |
| for many ad-hoc and tokenless clean-ups. But it could be convenient |
| for the common pattern of a type-directed clean-up. |
| |
| Automatically importing CoreFoundation and C functions |
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| |
| CF APIs use ``CFErrorRef`` pretty reliably, but there are several |
| problems here: 1) the memory management rules for CFErrors are unclear |
| and potentially inconsistent. 2) we need to know when an error is |
| raised. |
| |
| In principle, we could import POSIX functions into Swift as throwing |
| functions, filling in the error from ``errno``. It's nearly impossible |
| to imagine doing this with an automatic import rule, however; much |
| more likely, we'd need to wrap them all in an overlay. |
| |
| In both cases, it is possible to pull these into the Swift error |
| handling model, but because this is likely to require massive SDK |
| annotations it is considered out of scope for iOS 9/OS X 10.11 & Swift 2.0. |
| |
| Unexpected and universal errors |
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| |
| As discussed above, we believe that we can extend our current model to |
| support untyped propagation for universal errors. Doing this well, |
| and in particular doing it without completely sacrificing code size |
| and performance, will take a significant amount of planning and |
| insight. For this reason, it is considered well out of scope for |
| Swift 2.0. |
| |