Can nested do-catch blocks explicitly work?

 // [0] do throwable tests, then non-throwable mutation
 // (The tests are from a predicate that's a parameter for this function.)
 do {
  // [1] do throwable tests, then non-throwable mutation

  do {
    // [2] do throwable tests, then non-throwable mutation
  } catch {
    // [-1] undo the mutable part of [1]
    throw error
  }
 } catch {
    // [-0] undo the mutable part of [0]
    throw error  // "A function declared 'rethrows' may only throw if its parameter does"
 }

Why can't nested throwing cascade? Or am I doing something wrong?

Edit: I tried "catch let someIdentfier," with different names for each level. Didn't work.

You didn't show the whole fragment. This compiles ok:

func foo() throws {
    do {
        do {
            //
        } catch {
            throw error
        }
    } catch {
        throw error
    }
}
1 Like

This shows the error the OP is seeing, and I think makes it clear why that error occurs (the compiler can't trace that the exception caught in the outer block can only come from the throw in the inner block, which can only be a rethrow).

func doStuff(
    with closure: () throws -> Void
) rethrows -> Void {
    do {
        do {
            try closure()
        } catch {
            throw error
        }
    } catch {
        throw error  // "A function declared 'rethrows' may only throw if its parameter does"
    }
}

Typed throws should solve the issue, but does not:

func doStuff<E: Error>(
    with closure: () throws(E) -> Void
) throws(E) -> Void {
    do {
        do {
            try closure()
        } catch {
            throw error
        }
    } catch {
        throw error //  error: thrown expression type 'any Error' cannot be converted to error type 'E'
    }
}

Filed Typed throws doesn't infer error type for do/catch block · Issue #75260 · swiftlang/swift · GitHub for the latter (typed throws) problem. Since rethrows is more or less deprecated now, I don't know anyone will care to fix the problem as originally stated.

Using typed throws in Swift 6, you need to explicitly annotate the typed throw for each [edit: at least the outer] do block:

func doStuff<E: Error>(
    with closure: () throws(E) -> Void
) throws(E) -> Void {
    do throws(E) {
        do /* throws(E) */ {
            try closure()
        } catch {
            throw error
        }
    } catch {
        throw error
    }
}

Type inference will change with the FullTypedThrows feature to support this without annotation, but that is not yet available until a future version.

1 Like

As it stands right now, it's only necessary for the outermost do.

func doStuff<E: Error>(
  with closure: () throws(E) -> Void
) throws(E) -> Void {
  do throws(E) {
    do {
      try closure()
    } catch {
      do {
        try closure()
      } catch {
        throw error
      }
      throw error
    }
  } catch {
    throw error
  }
}
1 Like