I'm not defining the errors like DBConnectionError or OutOfMemoryError - it's the framework/platform which defines them and throws/returns them.
> But the reality is that the user not being there isn't exceptional.
That depends. In some contexts it is not exceptional (getting user by ID given as an argument to webservice), in that case using Maybe type is great. In other contexts it is very much exceptional (e.g. signed JWT refers to a non-existing user) and throwing Exception makes more sense.
> What about Error? Reality is that I don't really care. Error doesn't contain anything that is actionable. Either the whole chain succeeds and returns a valid result or the Error. The caller wants to find a user by id. There is one, or there isn't. All the rest is just errors that they'll pass on too. And in the end they get logged and result into Error 500.
Which is the same as exception. But now you have this visual noise of something you claim you don't care about. An unchecked exception makes this irrelevant noise go away.
> Now, if I were to use a throw new UserNotFoundException() for no user found you end up with generic try catches catching too much. And now someone needs to go and take it all apart to identify that single Exception that they want to deal with separately.
try {
...
}
catch (UserNotFoundException e) {
// handle ...
}
I'm catching exactly the exception I want. Where I'm catching too much? Where do I need to take it apart?(This particular example of exception usage is bad, though, as it smells of exception control flow. Here using Maybe type would be better)
> Whereas if I want to add a state in my enum the callers _MUST_ update their code due to how Rust works.
Which is good for some cases where the enum describes "business" cases.
But it is pretty bad for truly exceptional cases (which are unlikely to be handled anyway). Library adding a new exceptional case will break its clients, which seems like a bad trade-off (again, for truly exceptional cases).