Struct conforming to Error isn't caught by catch block

I'm trying to unify some common error types in my project and hence thought I'd introduce a NetworkError in my project. This is quite a large project and is hence modularised. I've introduced the NetworkError type in of my module (let's call it MyLibrary).

In MyLibrary:

public struct NetworkError: Error {
  public init() {}
}

I've declared NetworkError as a struct to simplify the call-sites so I can simply use is NetworkError when checking the errors, I don't have to check against an exact enum case. I also find an enum the wrong abstraction here as this error will only ever have a single case. This matches the implementation of CancellationError in the standard library.

I have a method in my iOS app target (let's call this target MyApp). I have a method in the app target which can throw a NetworkError.

In MyApp:

func throwingMethod() throws -> Bool {
  throw NetworkError()
}

I want to test that throwingMethod actually throws a NetworkError, so I've declared the following test in MyAppTests:

func testThrowingMethod() {
    do {
      _ = try throwingMethod()
      XCTFail("Method should've thrown")
    } catch is CancellationError {
      print("CancellationError")
    } catch is NetworkError {
      print("Success")
    } catch {
      XCTFail("Unexpected error \(error) thrown instead of \(NetworkError.self)")
    }
  }

Unexpectedly, the test fails. If I move the declaration of NetworkError into the app target, the test passes. If I move throwingMethod and its test to another library, while keeping NetworkError in MyLibrary, the test also passes.

If I change NetworkError to be an enum, the test passes.

Here's a minimal reproducible example project:

My real project has MyLibrary declared as an Xcode project in the same workspace as MyApp, so the issue isn't SPM specific, it was just easier to create a Swift package as a reproducible example.

I'm using Xcode 14.3.1 with Swift 5.8.

This seems like a bug in the runtime - does anyone have any ideas for a workaround until it's fixed in the language itself?

1 Like

Simply use an enum?

Your tests pass with Xcode 15.0, Swift 5.9. I don't have Xcode 14 to try. Do you have Xcode 15.0 to verify if that is the difference?

1 Like

As I've said in my original post, an enum with a single case feels like the wrong abstraction.

I've declared NetworkError as a struct to simplify the call-sites so I can simply use is NetworkError when checking the errors, I don't have to check against an exact enum case. I also find an enum the wrong abstraction here as this error will only ever have a single case. This matches the implementation of CancellationError in the standard library.

Use an enum with no cases?
I take that back as you won't be able constructing it then.

An enum with a single case is isomorphic to an empty struct, isn't it?


BTW, I tried your project on my copy of Xcode 14.3.1 and the test passed.

1 Like

@gestrich Xcode 15.1 with Swift 5.9 does seem to fix the issue indeed

1 Like