As typed throws are bouncing around my mind, I'm starting to envision functions that can only possibly throw errors that are relevant to the specific function being called.
A contrived example:
extension Data {
struct InvalidByteCount: Error {}
func convert<T: BinaryInteger>() throws(InvalidByteCount) -> T {
guard
MemoryLayout<T>.size == self.count
else { throw InvalidByteCount() }
return withUnsafeBytes { pointer in
pointer.load(as: T.self)
}
}
}
This gives me and the user an extremely clear means of knowing what can go wrong with a given input and a much narrower field of contingency plans to deal with errors. I think we all understand that :)
However, what happens when I call this from another method that has its own, potential unique errors? (another more contrived example):
extension Data {
struct InadequateDataForRequestedTotal: Error {}
func extractCountedArray<T: BinaryInteger>(total: Int) throws -> [T] {
let size = MemoryLayout<T>.size
let requiredByteCount = size * total
guard
count >= requiredByteCount
else { throw InadequateDataForRequestedTotal() }
var accumulator: [T] = []
for offset in stride(from: 0, to: total, by: size) {
let slice = self[offset..<(offset + size)]
try accumulator.append(slice.convert())
}
return accumulator
}
}
Now I have two different error types and have to use the generic throws
.
I am completely aware I could use an enum to cover all the possible errors for this suite of functions, but then I would have the potential to throw .inadequateDataForRequestedTotal
from the convert
method.
extension Data {
enum ExampleErrors: Error {
case invalidByteCount
case inadequateDataForRequestedTotal
}
// pretend the previous examples are rewritten to use this error enum instead
func exampleCallSite() {
do {
let value: Int64 = try data.convert()
} catch .invalidByteCount {
// handling code
} catch .inadequateDataForRequestedTotal {
// this makes no sense. The method has no path to throw error, yet I still have to handle it.
}
}
}
What are other people doing for this scenario? Any good solutions? Or are we still figuring this one out?
I briefly had the idea to constraint protocols together, which I concluded probably wouldn't even work in the end, but this doesn't even compile... Shouldn't it at least compile?
extension Data {
protocol ConversionError: Error {}
struct InvalidByteCount: ConversionError {}
func example() throws(ConversionError) {} // gets compiler error `Thrown type 'any Data.ConversionError' does not conform to the 'Error' protocol`
}