So if you aren't using exceptions for all errors, then you need an error handling strategy for those non-exception errors such as parsing failures and 404s. Which brings us back to Rust's strategy: the Result type.
Those languages have choices: you can use exception handling or not, depending on the API context. Exceptions should be used exceptional errors. For example, even in the file exists case, you can have an API to check existence (Java and C# do!), but maybe the file is deleted between that check and the open call...
Now without exceptions, you still have an existence check AND have an error-encoding result on open that is rarely in failure mode. Very annoying. You must combine the two calls even if separating them makes more sense.
Sorry... I want to make sure I have this right. Is your argument that exceptions would a better idea really that the version with sum types doesn't let you get away with the common antipattern of checking for file existence before opening it? Because what you should actually do in that situation (not, I stress, just in Rust, but in any language!) is just try to open the file, which makes the check-and-open operation atomic and results in a single error call. In other words, this is an example of how the Result type guides you towards the correct solution--which is pretty much the strongest reason I can think of for including a feature in a language.
I'm saying, for the file example, you might want to check existence first anyways regardless as part of feedback to the user. You might not even want to open the file, just present it in a list of files that could be open.
I get it that Rust is designed for the command line where that doesn't really occur.
I find this example extremely unconvincing. If you're not talking about an immediate check (for example, you have a list of files in a sidebar), it's quite possible that it hasn't been refreshed for a long while; there's no particular reason to assume that the file is still there. And even if it is, there are many other errors that can occur when you open a file (insufficient permissions, for example). In that situation, I don't see why it's better to fail with an (unchecked) exception, which the developer is going to have to remember to catch and try to tie back to the originating event (which might be quite tricky, given how much I/O occurs in something like an editor), than to require the developer to handle the potential failure immediately (by popping up a dialog box saying the file was deleted, for example).
In any case, what you're talking about is hardly the common case to optimize for. In basically every language I've ever used, the majority of files were opened without any user interaction, in which case Rust's API is absolutely the correct approach.
That is not to say that there aren't times where you have to do a redundant check, but generally speaking they only occur when you have control over the entire system under consideration and statically know (perhaps can even prove) that the error can't happen. In that situation, you should of course unwrap(), as there is no good way to recover from a logic bug in the existing program.
> If you're not talking about an immediate check (for example, you have a list of files in a sidebar), it's quite possible that it hasn't been refreshed for a long while; there's no particular reason to assume that the file is still there.
Exactly. It is probably still there, just someone could have deleted it in the few seconds or so that it took for the user to make an action. UIs are filled with a bunch of invariants that are probably true but not necessarily so. 99% of these programs will just exception out if say that file is deleted between the non-atomic time it takes to do verification and action. And there is nothing considered very wrong with that: you screw with the environment of an application while its running, bad things are expected to happen.
Now, a language like Rust expects that everything is atomic in the normal case. But since users aren't very batchy, that means re-verifying things over and over again. It is not the overhead that is the problem here, the fact that the check is redundant is not the issue. The fact that the programmer is pestered into handling these cases is a huge issue: Java/C# will just exception out and that's the end of it (you don't really want to bother trying to handle that file being deleted in the few seconds it took for the user to make a decision, or if you deal with it, deal with it at a very coarse granularity).
Rust is not designed for writing interactive programs, it is designed for batchy systems code where "files are opened without any user interaction." But let's not discount why existing languages that are designed for writing user facing code make the decisions that they made.
> 99% of these programs will just exception out if say that file is deleted between the non-atomic time it takes to do verification and action. And there is nothing considered very wrong with that: you screw with the environment of an application while its running, bad things are expected to happen.
I would much rather strive to always do the right thing, if possible, because I do consider that wrong. The pervasiveness of the `Result` type in Rust makes it much easier for me to do so; the cost to you is that you must write `unwrap()` instead of nothing. On the other hand, if unchecked exceptions are the error handling default, I have no recourse in order to discover what possible errors can occur from a function call other than to read the function's source code, while you just save typing `unwrap()`. To me, Rust's solution seems like an eminently reasonable tradeoff.
I get that philosophy, it works wonderfully in batch non-interactive environments. But when almost everything is occurring non-atomically, you wind up throwing unwrap everywhere since nothing is actually guaranteed, the state of the world can change between any operation, even if that is unlikely.
I've specialized in very interactive systems so my world is quite messy. When you run a compiler (and the program being compiled) while the user is editing it, there are lots of transient error conditions to deal with. I just couldn't imagine writing such a system in Rust, error propagation and resolution is a much more global affair that can't tunnel explicitly through function signatures. But then, I understand that Rust wasn't designed for my problems.