It looks like the error types need to be explicit in the function signatures in interfaces, but not in the function signatures of the implementations of the interface or in the signatures of anything calling those interface functions. That would give the compiler the information it needed without getting into the Java checked exception hell, yes?
Again, I'm not a language designer or a Zig expert. But here's an example in my head of what something Boomla-ish could look like (noting that I haven't seen Boomla before today so it might look more like Go).
type SqlRunner interface {
Select(string stmt) ([]interface{}) (SQLSyntaxError, SQLTimeoutError)
}
fn getData(runner SqlRunner) ([]interface{}) (error) {
if (day == Monday) {
fail NoMondays
}
return runner.Select("SELECT * FROM table")
}
Any implementation of SqlRunner can only fail with one of those three errors or return a slice of interfaces. The compiler can infer which errors getData can return - NoMondays, SQLSyntaxErro, or SqlTimeoutError. Even if someone adds another error to the interface, the compiler can still infer it.
What is nice is that wherever you're handling the error would now need to be updated - because you're now not handling one of the errors you're supposed to be handling. It doesn't put you into refactoring hell like Java where every method signature needs to be updated, but it does alert you where your code is now wrong because you aren't handling something.
That seems like it'd work without being problematic like Java's exceptions.
If it still seems like a problem because maybe you want to implement SqlRunner with different errors, I might wonder if it's really the same interface. Certainly if you wanted to implement it as `Select(string stmt) (int)` we'd both agree that's not the same interface. Your caller isn't expecting an int back. But if they aren't expecting a WontSelectId5 error back, isn't that kinda the same thing? If I have a SqlRunner, I might think "I want to retry on SQLTimeoutErrors and show the user an error on SQLSyntaxErrors since there's no point in retrying that." What do I do with an unexpected WontSelectId5? If I'm expecting it, I won't retry it.
If you really want to keep the interface open, maybe you could use `anyerror`. Zig has a special `anyerror` to match any error:
Select(string stmt) ([]interface{}) (SQLSyntaxError, SQLTimeoutError, anyerror)
Everything upstream would still just have (error) in the signature and the compiler could still help you out - to an extent. For example, your switch could look like:
switch (err) {
SQLSyntaxError => a,
SQLTimeoutError => b,
anyerror => c
}
That would allow for an interface implementation to have its own custom errors while the interface still defines types for the ones that might happen most often across implementations.
I'm curious if you'd want the interface to have an `anyerror` addition. It allows for random errors to be returned, but maybe that makes it a different thing or that the interface needs to be updated (and updating the interface wouldn't be a refactoring hell since everything up the call stack would would get inferred by the compiler.
> Go/Boomla errors can also have dedicated types, so you don't need to compare error strings
I guess I just saw `errors.New` a lot in Go, but part of that might be that I haven't used Go in the past few years. It sounds like maybe there's a shift to using types and `errors.Is` to check?