Allow Error protocol to work with @ rethrows

This proposal expands on the new Rethrowing protocol conformances feature.

Currently when building generic types specializing with Error protocol, often we have to add specilaized case for Never type. Making Error rethrowing allows single implementation in such cases.

The throw Error syntax can be modified to only thow based on whether error type is not Never. One real world use case for such example will be Result type. Currently Result.get() always throws regardless of error type. It can be modified to only throw for non-Never error type:

extension Result {
    func get() rethrows -> Success {
        switch self {
        case .success(let value):
            return value
        case .failure(let error):
            throw error
        }
    }
}

The usage of such implementation will be like this:

// If Error type is Never
let noThrowValue = result.get()
// If Error type is other than Never
let throwValue = try result.get()

rethrows doesn’t make sense for get(), because it doesn’t take a closure argument. Did you mean throws, or are you proposing a new meaning for rethrows?

I think there’s an even easier solution to your problem without introducing a new language feature: offer a non-throwing version of get when Failure is Never:

extension Result where Failure == Never {
  func get() -> Success {
    switch self {
      case .success(let x): return x
      case .failure(_): fatalError("unreachable")
    }
  }
}
1 Like

There are definitely uses where it does make sense to have that rethrowing: swift-async-algorithms/Rethrow.swift at main · apple/swift-async-algorithms · GitHub.

That hack we are using allows the cases where we would like to write rethrows(unchecked); where we promise that the throwing is sourced from some sort of generic system of input sources of throwing.

3 Likes

There is even no need for fatalError:

extension Result where Failure == Never {
  func get() -> Success {
    switch self {
    case .success(let success): return success
    case .failure(let failure): switch failure {}
    }
  }
}
4 Likes

You don’t need the failure case either as it’s unreachable.

extension Result where Failure == Never {
  func get() -> Success {
    switch self {
      case let .success(value): return value
    }
  }
}

is sufficient.

11 Likes

No need to rethrows. Just wait for precise error typing. Then it will become func get() throws Failure -> Success and the compiler will happily tell you that you don't need to type try when Failure is Never.

Although it might not be able to change the concrete error type on the get method. We will likely get a different property with a precise throwing accessor.

rethrows doesn’t make sense for get() , because it doesn’t take a closure argument. Did you mean throws , or are you proposing a new meaning for rethrows ?

@ksluder the language feature for this is already implemented as part of this pitch. This discussion is just for Error protocol to confirm to this existing feature. I have added detail in my original post as well.

I think there’s an even easier solution to your problem without introducing a new language feature: offer a non-throwing version of get when Failure is Never:

  extension Result where Failure == Never {
    func get() -> Success {
      switch self {
        case .success(let x): return x
        case .failure(_): fatalError("unreachable")
      }
    }
  }

This is exactly what I want to be addressed, instead of adding separate implementation for Never type for every generic type, using existing language feature this can be handled with the same implementation.

This whole exchange had me on the edge of my seat. And what a beautiful conclusion :sparkles:

1 Like