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

Reply via email to