I don’t get why that type checks: 1. rescue always throws inside sync, therefore 2. _syncHelper inside sync always throws, therefore 3. sync always throws, therefore 4. Shouldn’t sync be declared throws not rethrows?
-- Howard. On 1 Jan 2018, at 1:10 pm, Charles Srstka via swift-users <swift-users@swift.org> wrote: >> On Dec 31, 2017, at 1:35 AM, Nevin Brackett-Rozinsky via swift-users >> <swift-users@swift.org> wrote: >> >> So, I know this doesn’t actually address your issue, but I think it is >> important to clarify that “rethrows” does not guarantee anything about the >> *type* of error thrown by a function. What “rethrows” implies is that the >> function *will not throw* unless at least one of its arguments throws. >> >> In particular, if the argument throws, the outer function can throw *any >> error or none at all*. For example, >> >> enum CatError: Error { case hairball } >> enum DogError: Error { case chasedSkunk } >> >> func foo(_ f: () throws -> Void) rethrows -> Void { >> do { try f() } >> catch { throw CatError.hairball } >> } >> >> do { >> try foo{ throw DogError.chasedSkunk } >> } catch { >> print(error) // hairball >> } >> >> Inside foo’s catch block, it is legal to throw any error, or not throw an >> error at all. But *outside* that catch block foo cannot throw, which is >> causing you consternation. >> >> • • • >> >> I don’t have a good solution for you, but in attempting to find one I *did* >> uncover something which compiles that probably shouldn’t. It seems that a >> “rethrows” function is currently allowed to throw if a *local* function >> throws: >> >> func rethrowing(_ f: () throws -> Void) rethrows -> Void { >> func localThrowing() throws -> Void { throw CatError.hairball } >> return try localThrowing() >> } >> >> do { >> try rethrowing{ throw DogError.chasedSkunk } >> } catch { >> print(error) // hairball >> } >> >> I wouldn’t count on this functionality as it is most likely a bug. Indeed, >> if we pass in a non-throwing argument then we get a runtime error: >> >> rethrowing{ return } // EXC_BAD_ACCESS (code=1, address=0x0) >> >> Although, if we change “localThrowing” to use do/catch on a call to “f” and >> throw only in the catch block, or even use your “var caught: Error?” trick, >> then it appears to work as intended with no problems at runtime. >> >> • • • >> >> In the unlikely scenario that the above local-function behavior is valid and >> intended, the following function will, technically speaking, let you work >> around the issue you’re having: >> >> func withPredicateErrors <Element, Return> >> (_ predicate: (Element) throws -> Bool, >> do body: @escaping ((Element) -> Bool) -> Return >> ) rethrows -> Return >> { >> func bodyWrapper(_ f: (Element) throws -> Bool) throws -> Return { >> var caught: Error? >> let value = body{ elem in >> do { >> return try f(elem) >> } catch { >> caught = error >> return true >> } >> } >> if let caught = caught { throw caught } >> return value >> } >> >> return try bodyWrapper(predicate) >> } >> >> It is not pretty, and it probably relies on a compiler bug, but at the >> present time, against all odds, it look like this operates as you intend. >> >> Nevin > > That’s not the only exploit of that sort to exist in the frameworks, > actually—and if you look through the source code to the standard library, > you’ll see that the Swift standard library actually uses this kind of thing > from time to time. For example, take DispatchQueue.sync(), which is declared > as ‘rethrows’ despite wrapping a C function that Swift can’t reason anything > about. I’d always wondered how that worked, so at some point I looked it up. > Here’s how it's implemented: > > The public function we’re all used to, sync(): > > public func sync<T>(execute work: () throws -> T) rethrows -> T { > return try self._syncHelper(fn: sync, execute: work, rescue: { throw $0 }) > } > > This basically passes everything on to a private method, _syncHelper, which > looks like this: > > private func _syncHelper<T>( > fn: (() -> Void) -> Void, > execute work: () throws -> T, > rescue: ((Error) throws -> (T))) rethrows -> T > { > var result: T? > var error: Error? > withoutActuallyEscaping(work) { _work in > fn { > do { > result = try _work() > } catch let e { > error = e > } > } > } > if let e = error { > return try rescue(e) > } else { > return result! > } > } > > So basically, since every error thrown inside _syncHelper() is rethrown from > one of the closures passed into it, that satisfies ‘rethrows’, and it also > satisfies ‘rethrows’ for sync(), since anything thrown by it is thrown by > another method that’s also declared “rethrows”. Sneaky. > > Anyway, if you really need to do something like this, I’d recommend doing it > the way the Swift standard library does, because: > > 1. They can’t break that in the compiler without also breaking their own > code, and: > > 2. If they *do* break it because they’ve introduced a new, proper way to do > this using some kind of actuallyRethrows() function or something, you’ll want > to switch to that anyway. > > Charles > > _______________________________________________ > swift-users mailing list > swift-users@swift.org > https://lists.swift.org/mailman/listinfo/swift-users
_______________________________________________ swift-users mailing list swift-users@swift.org https://lists.swift.org/mailman/listinfo/swift-users