Rethrows issue

I need to do something like this:

  func withPredicateErrors<Element, Return>(_ predicate: (Element) throws -> Bool, do body: ((Element) -> Bool) -> Return) rethrows -> Return {
    var caught: Error?
    let value = body { elem in
      do {
        return try predicate(elem)
      }
      catch {
        caught = error
        return true // Terminate search
      }
    }
    
    if let caught = caught {
      throw caught
    }
    else {
      return value
    }
  }

The problem is, the Swift compiler doesn't allow the explicit `throw` statement; even though it can only throw errors originally thrown by `predicate`, the compiler is not smart enough to prove that to itself. I cannot make `body` a `throws` function.

Is there any way to do this? Either to override the compiler's safety check, or to rewrite this function to avoid it?

···

--
Brent Royal-Gordon
Architechies

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

···

On Sat, Dec 30, 2017 at 11:15 PM, Brent Royal-Gordon via swift-users < swift-users@swift.org> wrote:

I need to do something like this:

        func withPredicateErrors<Element, Return>(_ predicate: (Element)
throws -> Bool, do body: ((Element) -> Bool) -> Return) rethrows -> Return {
          var caught: Error?
          let value = body { elem in
            do {
              return try predicate(elem)
            }
            catch {
              caught = error
              return true // Terminate search
            }
          }

          if let caught = caught {
            throw caught
          }
          else {
            return value
          }
        }

The problem is, the Swift compiler doesn't allow the explicit `throw`
statement; even though it can only throw errors originally thrown by
`predicate`, the compiler is not smart enough to prove that to itself. I
cannot make `body` a `throws` function.

Is there any way to do this? Either to override the compiler's safety
check, or to rewrite this function to avoid it?

--
Brent Royal-Gordon
Architechies

_______________________________________________
swift-users mailing list
swift-users@swift.org
https://lists.swift.org/mailman/listinfo/swift-users

How about this?

func withPredicateErrors<Element, Return>(
    _ predicate: @escaping (Element) throws -> Bool,
    do body: ((Element) -> Bool) -> Return
    ) rethrows
    -> Return
{
    var caught: Error?
    var element: Element?
    
    let value = body { elem in
        element = elem
        do {
            return try predicate(elem)
        }
        catch {
            caught = error
            return true // Terminate search
        }
    }
    
    if let _ = caught,
        let element = element {
        try _ = predicate(element)
    }

    return value
}

-Kenny

···

On Dec 30, 2017, at 8:15 PM, Brent Royal-Gordon via swift-users <swift-users@swift.org> wrote:

I need to do something like this:

  func withPredicateErrors<Element, Return>(_ predicate: (Element) throws -> Bool, do body: ((Element) -> Bool) -> Return) rethrows -> Return {
    var caught: Error?
    let value = body { elem in
      do {
        return try predicate(elem)
      }
      catch {
        caught = error
        return true // Terminate search
      }
    }
    
    if let caught = caught {
      throw caught
    }
    else {
      return value
    }
  }

The problem is, the Swift compiler doesn't allow the explicit `throw` statement; even though it can only throw errors originally thrown by `predicate`, the compiler is not smart enough to prove that to itself. I cannot make `body` a `throws` function.

Is there any way to do this? Either to override the compiler's safety check, or to rewrite this function to avoid it?

--
Brent Royal-Gordon
Architechies

_______________________________________________
swift-users mailing list
swift-users@swift.org
https://lists.swift.org/mailman/listinfo/swift-users

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()
}

Yikes, that looks bad; worth filing a bug at bugs.swift.org if there isn't
one already.

···

On Sat, Dec 30, 2017 at 11:35 PM, Nevin Brackett-Rozinsky via swift-users < swift-users@swift.org> wrote:

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

On Sat, Dec 30, 2017 at 11:15 PM, Brent Royal-Gordon via swift-users < > swift-users@swift.org> wrote:

I need to do something like this:

        func withPredicateErrors<Element, Return>(_ predicate: (Element)
throws -> Bool, do body: ((Element) -> Bool) -> Return) rethrows -> Return {
          var caught: Error?
          let value = body { elem in
            do {
              return try predicate(elem)
            }
            catch {
              caught = error
              return true // Terminate search
            }
          }

          if let caught = caught {
            throw caught
          }
          else {
            return value
          }
        }

The problem is, the Swift compiler doesn't allow the explicit `throw`
statement; even though it can only throw errors originally thrown by
`predicate`, the compiler is not smart enough to prove that to itself. I
cannot make `body` a `throws` function.

Is there any way to do this? Either to override the compiler's safety
check, or to rewrite this function to avoid it?

--
Brent Royal-Gordon
Architechies

_______________________________________________
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

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

···

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

2 Likes

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