IIRC, one of the main goals in Swift 3 was to further stabilize the language spec and *minimize* breakage in future versions, not eliminate potential breakage altogether. Considering the numerous directions Native Concurrency can take (Go-style green thread scheduling, async-await, continuation handler transforms, etc), it would be much better if that discussion got started first. Asynchronous error handling will likely be a big talking point of said discussion anyway.
Dan > On Jul 14, 2016, at 3:30 PM, Charles Srstka <cocoa...@charlessoft.com> wrote: > > Right, but since this would affect the Obj-C importer and thus would be a > source-breaking change, it would probably not be possible anymore after Swift > 3. > > Charles > >> On Jul 14, 2016, at 4:57 PM, Dan Stenmark <daniel.j.stenm...@gmail.com> >> wrote: >> >> I’d say it’s a little premature to be talking about this; the team has made >> it very clear that the discussion on Native Concurrency in Swift won’t begin >> for another couple months. >> >> Dan >> >>> On Jul 14, 2016, at 10:54 AM, Charles Srstka via swift-evolution >>> <swift-evolution@swift.org> wrote: >>> >>> I know it’s late, but I was wondering what the community thought of this: >>> >>> MOTIVATION: >>> >>> With the acceptance of SE-0112, the error handling picture looks much >>> stronger for Swift 3, but there is still one area of awkwardness remaining, >>> in the area of returns from asynchronous methods. Specifically, many >>> asynchronous APIs in the Cocoa framework are declared like this: >>> >>> - (void)doSomethingWithFoo: (Foo *)foo completionHandler: (void (^)(Bar * >>> _Nullable, NSError * _Nullable))completionHandler; >>> >>> This will get imported into Swift as something like this: >>> >>> func doSomething(foo: Foo, completionHandler: (Bar?, Error?) -> ()) >>> >>> The intention of this API is that either the operation will succeed, and >>> something will be passed in the Bar parameter, and the error will be nil, >>> or else the operation will fail, and then the error parameter will be >>> populated while the Bar parameter is nil. However, this intention is not >>> expressed in the API, since the syntax leaves the possibility that both >>> parameters could be nil, or that they could both be non-nil. This forces >>> the developer to do needless and repetitive checks against a case which in >>> practice shouldn’t occur, as below: >>> >>> doSomething(foo: foo) { bar, error in >>> if let bar = bar { >>> // handle success case >>> } else if let error = error { >>> self.handleError(error) >>> } else { >>> self.handleError(NSCocoaError.FileReadUnknownError) >>> } >>> } >>> >>> This results in the dreaded “untested code.” >>> >>> Note that while it is possible that the developer could simply force-unwrap >>> error in the failure case, this leaves the programs open to crashes in the >>> case where a misbehaved API forgets to populate the error on failure, >>> whereas some kind of default error would be more appropriate. The >>> do/try/catch mechanism works around this by returning a generic _NilError >>> in cases where this occurs. >>> >>> PROPOSED SOLUTION: >>> >>> Since the pattern for an async API that returns an error in the Cocoa APIs >>> is very similar to the pattern for a synchronous one, we can handle it in a >>> very similar way. To do this, we introduce a new Result enum type. We then >>> bridge asynchronous Cocoa APIs to return this Result type instead of >>> optional values. This more clearly expresses to the user the intent of the >>> API. >>> >>> In addition to clarifying many Cocoa interfaces, this will provide a >>> standard format for asynchronous APIs that return errors, opening the way >>> for these APIs to be seamlessly integrated into future asynchronous >>> features added to Swift 4 and beyond, in a way that could seamlessly >>> interact with the do/try/catch feature as well. >>> >>> DETAILED DESIGN: >>> >>> 1. We introduce a Result type, which looks like this: >>> >>> enum Result<T> { >>> case success(T) >>> case error(Error) >>> } >>> >>> 2. Methods that return one parameter asynchronously with an error are >>> bridged like this: >>> >>> func doSomething(foo: Foo, completionHandler: (Result<Bar>) -> ()) >>> >>> and are used like this: >>> >>> doSomething(foo: foo) { result in >>> switch result { >>> case let .success(bar): >>> // handle success >>> case let .error(error): >>> self.handleError(error) >>> } >>> } >>> >>> 3. Methods that return multiple parameters asynchronously with an error are >>> bridged using a tuple: >>> >>> func doSomething(foo: Foo, completionHandler: (Result<(Bar, Baz)>) -> ()) >>> >>> and are used like this: >>> >>> doSomething(foo: foo) { result in >>> switch result { >>> case let .success(bar, baz): >>> // handle success >>> case let .error(error): >>> self.handleError(error) >>> } >>> } >>> >>> 4. Methods that return only an error and nothing else are bridged as they >>> are currently, with the exception of bridging NSError to Error as in >>> SE-0112: >>> >>> func doSomething(foo: Foo, completionHandler: (Error?) -> ()) >>> >>> and are used as they currently are: >>> >>> doSomething(foo: foo) { error in >>> if let error = error { >>> // handle error >>> } else { >>> // handle success >>> } >>> } >>> >>> 5. For the case in part 2, the bridge works much like the do/try/catch >>> mechanism. If the first parameter is non-nil, it is returned inside the >>> .success case. If it is nil, then the error is returned inside the .error >>> case if it is non-nil, and otherwise _NilError is returned in the .error >>> case. >>> >>> 6. For the case in part 3, in which there are multiple return values, the >>> same pattern is followed, with the exception that we introduce a new >>> Objective-C annotation. I am provisionally naming this annotation >>> NS_REQUIRED_RETURN_VALUE, but the developer team can of course rename this >>> annotation to whatever they find appropriate. All parameters annotated with >>> NS_REQUIRED RETURN_VALUE will be required to be non-nil in order to avoid >>> triggering the error case. Parameters not annotated with NS_REQUIRED >>> RETURN_VALUE will be inserted into the tuple as optionals. If there are no >>> parameters annotated with NS_REQUIRED RETURN_VALUE, the first parameter >>> will be implicitly annotated as such. This allows asynchronous APIs to >>> continue to return optional secondary values if needed. >>> >>> Thus, the following API: >>> >>> - (void)doSomethingWithFoo: (Foo *)foo completionHandler: (void (^)(Bar * >>> _Nullable NS_REQUIRED_RETURN_VALUE, Baz * _Nullable >>> NS_REQUIRED_RETURN_VALUE, NSError * _Nullable))completionHandler; >>> >>> is bridged as: >>> >>> func doSomething(foo: Foo, completionHandler: (Result<(Bar, Baz)>) -> ()) >>> >>> returning .success only if both the Bar and Baz parameters are non-nil, >>> whereas this API: >>> >>> - (void)doSomethingWithFoo: (Foo *)foo completionHandler: (void (^)(Bar * >>> _Nullable NS_REQUIRED_RETURN_VALUE, Baz * _Nullable, NSError * >>> _Nullable))completionHandler; >>> >>> is bridged as: >>> >>> func doSomething(foo: Foo, completionHandler: (Result<(Bar, Baz?)>) -> ()) >>> >>> returning .success whenever the Bar parameter is nil. An API containing no >>> parameter annotated with NS_REQUIRED_RETURN_VALUE will be bridged the same >>> as above. >>> >>> FUTURE DIRECTIONS: >>> >>> In the future, an asynchronous API returning a Result could be bridged to >>> an async function, should those be added in the future, using the semantics >>> of the do/try/catch mechanism. The bridging would be additive, similarly to >>> how Objective-C properties declared via manually written accessor methods >>> can nonetheless be accessed via the dot syntax. Thus, >>> >>> func doSomething(_ completionHandler: (Result<Foo>) -> ()) >>> >>> could be used as if it were declared like this: >>> >>> async func doSomething() throws -> Foo >>> >>> and could be used like so: >>> >>> async func doSomethingBigger() { >>> do { >>> let foo = try await doSomething() >>> >>> // do something with foo >>> } catch { >>> // handle the error >>> } >>> } >>> >>> making asynchronous APIs convenient to write indeed. >>> >>> ALTERNATIVES CONSIDERED: >>> >>> Leaving the somewhat ambiguous situation as is. >>> >>> Charles >>> >>> _______________________________________________ >>> swift-evolution mailing list >>> swift-evolution@swift.org >>> https://lists.swift.org/mailman/listinfo/swift-evolution >> >
_______________________________________________ swift-evolution mailing list swift-evolution@swift.org https://lists.swift.org/mailman/listinfo/swift-evolution