Typed throw in a generic function inside a protocol can't find error type in scope

Using Swift 6 and typed throws, I've noticed that this works:

func foobar<Failure: Error>(closure: () throws(Failure) -> Void) throws(Failure) {}

So does this:

protocol FoobarProtocol {
  associatedtype Failure: Error

  func foobar(closure: () throws(Failure) -> Void) throws(Failure)
}

But this doesn't work:

protocol FoobarProtocol {
  func foobar<Failure: Error>(closure: () throws(Failure) -> Void) throws(Failure)
}

Compilation fails with the following error:

<stdin>:2:75: error: cannot find type 'Failure' in scope
1 | protocol FoobarProtocol {
2 |   func foobar<Failure: Error>(closure: () throws(Failure) -> Void) throws(Failure)
  |                                                                           `- error: cannot find type 'Failure' in scope
3 | }
<stdin>:2:75: error: thrown type '<<error type>>' does not conform to the 'Error' protocol
1 | protocol FoobarProtocol {
2 |   func foobar<Failure: Error>(closure: () throws(Failure) -> Void) throws(Failure)
  |                                                                           `- error: thrown type '<<error type>>' does not conform to the 'Error' protocol
3 | }

If I remove the second throws(Failure) so that only the closure is throwing and not the function itself, it builds fine. rethrows keyword works too, which also does what I intended to do here.

Is that normal? Is this supposed to work, or are we expected to use associated types in protocols for typed throws? This looks like a Swift bug to me.

Thanks!

2 Likes

Good catch. The error generated is the same as if you used a non-protocol named type and left off the body ({}).

This is the simplest reduction:

enum NonProtocolType {
  func f<E>(_: E) throws(E) {} // Compiles. 
}

Delete {} for:
Cannot find type 'E' in scope
Thrown type '<<error type>>' does not conform to the 'Error' protocol

instead of the typical Expected '{' in body of function declaration.


Perhaps useful to note is that, because the problem is related to the opening curly brace, protocol extensions don't exhibit the problem.

protocol FoobarProtocol { }

extension FoobarProtocol {
  // Compiles.
  func foobar<Failure>(closure: () throws(Failure) -> Void) throws(Failure) { }
}
1 Like

I think this is a bug. As a workaround, you can explicitly specify the return type, and it'll compile.

// No error.
protocol FoobarProtocol {
  func foobar<Failure: Error>(closure: () throws(Failure) -> Void) throws(Failure) -> Void
}
3 Likes

Indeed it does, I think I'll stick for rethrows for my simple case though, it's lighter than typed throws if -> Void is required.

Even in the case where you're only using typed throws to distinguish between Never and Error, rethrows is not as good of a choice. Please refrain!

struct X {
  func good<Failure>(closure: () throws(Failure) -> Void) throws(Failure) { }
  func bad(closure: () throws -> Void) rethrows { }
}

_ = X().good as (_) -> _ // Compiles.
_ = X().bad as (_) -> _ // Invalid conversion from throwing function…to non-throwing function…