Pitch: Genericizing over annotations like throws

To be fair, your example shows a protocol which already allows its next() method to throw and simply allows non-throwing methods to witness the requirement. It's convenient, but you don't need typed-throws for that; in fact, I think it already works.

I don't think the solution proposed by OP is all that useful - I think we actually have a better thing, right now (Swift 4.2), but I don't think everybody is aware of it, so let me explain:

You can create a parent protocol, whose requirements can throw, and also a non-throwing refinement. The compiler will already recognise these as being the same, so you don't even need to write a default implementation with the throwing signature (see example). However, generic code will be able to require a non-throwing witness if your algorithms are not fault-tolerant. I think it's a cleaner solution.

We could do something like this in the standard library, except that ABI stability bans re-parenting protocols, and Sequence/IteratorProtocol kind of have knives dangling above their heads already.

protocol MightThrowIterator {
  mutating func next() throws -> Int
}
protocol NoThrowIterator: MightThrowIterator {
  mutating func next() -> Int
}

struct A: MightThrowIterator {
  enum Err: Error { case anError }
  mutating func next() throws -> Int {
		throw Err.anError
  }
}

struct B: NoThrowIterator {
  mutating func next() -> Int {
    return 42
  }
}

func tryIterate<T: MightThrowIterator>(_ val: inout T) {
  do {
    let element = try val.next()
    print(element)
  } catch {
    print(error)
  }
}

func definitelyIterate<T: NoThrowIterator>(_ val: inout T) {
  let element = val.next()
  print(element)
}

func test() {
  var testObjA = A()
  var testObjB = B()
  tryIterate(&testObjA) // prints: 'anError'
  tryIterate(&testObjB) // prints: 42
  definitelyIterate(&testObjA) // Compile error: 'A' does not conform to expected type 'NoThrowIterator'
  definitelyIterate(&testObjB) // prints: 42
}
1 Like