Type checking hole regarding `rethrows`

The following code compiles fine but will produce a guaranteed crash when used at runtime.

func mayThrow(_ op: () throws -> Void) rethrows {
  func mustThrow() throws {
    try op()  // <- even this line is not need to show case this problem
    throw MyError()
  }
  try mustThrow()
}

Now, no matter how you try to call the above function, an error will escape and cause a crash.

try? mayThrow()  // crash

// nor does this work
do {
  try mayThrow {} // also crash
} catch { }

The compiler will complain there's nothing to "try" though.

In conclusion, it seems at the moment the compiler does not correctly type-check rethrow proprgation considering local functions.

There are known issues with rethrows-checking, but they can’t be fixed because the problem is (ab)used by standard functions like DispatchQueue.sync, which follow the spirit of rethrows (only throw if the argument does) but in a way that a more sound implementation wouldn’t allow.

This isn’t really going to matter soon, after typed throws become a thing (if they aren’t already?) and you can say:

func mayThrow<E: Error>(_ op: () throws(E) -> Void) throws(E) {
  try op() // okay
  throw MyError() // error, MyError is not E
}

Thanks for your background info.
Follow your words, I find some thorough discussion in the forum. (Pitch: Fix rethrows checking and add rethrows(unsafe))

And yes, I love the proposal of typed throws.
The thing that worries me is the fact that rethrows will likely always be in the language (because there are patterns out there which cannot be expressed by typed throws easily), and this pitfall will always be there.

Afaics this is a "simple" (might be hard to fix ;-) bug: The compiler should complain that mayThrow has to be marked with throws.

1 Like

My personal hope is that those patterns are sufficiently weird and rare that typed throws can completely replace rethrows in all new code, and one day we can deprecate rethrows.

The fix here is completely trivial, but it would break source compatibility, that's why we never landed it.

5 Likes

Imo it is a sad thing if Swift has reached a state where bugs are not fixed because of compability :frowning:

1 Like

It's about the cost vs benefit. For example, there were two minor source breaking fixes documented in CHANGELOG.md here: Document two minor source-breaking bug fixes in Swift 6.0 · apple/swift@582ec11 · GitHub. I think most people will agree that fixing these makes total sense even though it might theoretically result in an existing project no longer compiling.

With the rethrows thing here on the other hand there are existing usages of rethrows that were written because they specifically rely on this soundness hole. Fixing it would require introducing a rethrows(unsafe) escape hatch, or migrating the existing usages to typed throws, and it's just not worth the hassle at this point. If we didn't have typed throws, I would have advocated for fixing and extending rethrows, of course.

2 Likes

Would it be reasonable to add a warning for this case, now that we have typed throws?