PS: I was the co-author of it.
Over an RPC boundary it seems impossible that there could be any scenario so serious that is so unexpected and which it is not possible to form a contingency for that it would be necessary to propagate the exception across to the other side. It opens up a whole can of worms.
An inherent requirement for any contract language is a signal to refuse a service if the pre-conditions are not met (for example, refusing to withdraw money because of insufficient funds in an account).
The most pragmatic solution to this inherent feature of contracts are typed (checked) exceptions - anything else is a less robust, incomplete solution.
First of all for debugging it is a huge help to get as rich data back as you can from the other end and logged.
As for the proper handling of exceptions, I think there are a number of semantic attributes that an exception can have: for instance, transient/permanent, and the scope of the exception (is the whole system hosed? just this record? etc.)
In a greenfield application you can build an exception hierarchy that records this, but in the real world it is necessary to build a system that does some inference, or even guessing, to decide what to do about a failure.
I just remember the bad old days when C programs would return an int and it would be -1 if the function failed, or maybe they set errno, and either way you could double the size of some functions if you add in error checking and probably get it wrong.
Exceptions on the other hand are good for the normal kind of call stack and also work in "callback hell" situations since you can stuff them in a field or collection, pass them as a function method, etc.
Most of the time, when you're handling RPCs, you're processing the responses asynchronously anyway, which makes the concept of an exception (which unwinds the stack) superfluous anyway. You should absolutely be handling error conditions. That doesn't mean you need to use the language mechanisms of exceptions to do so.
CORBA got into trouble because it tried to make remote objects look too much like local objects. Don't think that way: think in terms of messages passed between communicating sequential processes, with an error indicating that the process will have to do something else.
How could I? I would have designed that interface long before implementing it. No, in that case, this is an unexpected system error - for example, an unchecked RuntimeException in Java.
Just because people don't know how to write software contracts properly, doesn't mean that languages and protocols should remove the notion of a checked exception.
When used properly, they are an essential part of an understandable contract. And that's before we even look at it from a type system point of view, where it's absolutely silly to use the same response message to represent either success of failure, greatly increasing the cognitive overhead of the developer calling your service.
A checked exception is okay but it is still an exception with all the stack frame capturing overhead that brings.
In better languages like ML variants (say F#) we have discriminated unions. These let us return structured, type safe, result codes and information. Without all the overhead and OO-ness of nasty exceptions. We can still throw exceptions but these are strictly, and I mean strictly, for those very exceptional and unexpected scenarios.
message FooResult {
optional Exception exception = 1;
optional Foo foo = 2;
}You have no clean way of describing multiple pre-conditions (refusal reasons), or to evolve those over time in existing systems, without assuming that developers will somehow correctly read your API documentation, and manually write the code everywhere to handle all possible exceptions, with no help from your compiler or your language's type system.
This is how to indicate exceptions in the 1970s. The world has moved on since then :-)
Modern languages have a compiler-checked sum type, and protobuf can support these (oneof). You get the good parts of exceptions (being able to cleanly separate error handling from happy-path code, and doing it concisely and readably via do-notation or equivalent) without the bad parts (invisible nonlocal control flow, breaking referential transparency, hard to abstract over).
(You still have the problem of returning a new type of error condition that your client code wasn't expecting, but it's the same problem as returning a new type of successful value that your client code wasn't expecting, and you can solve it in the exact same way).
I'd settle for 1990s tech over 1970s tech any day when it comes to contracts (APIs).
(There are certainly plenty of places stuck on older tech - but I'd think those places won't be interested in adopting something as new as GRPC)