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 > <[email protected]> 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 > [email protected] > https://lists.swift.org/mailman/listinfo/swift-evolution _______________________________________________ swift-evolution mailing list [email protected] https://lists.swift.org/mailman/listinfo/swift-evolution
