You are explaining the diferrence between error results and panics in Rust, and maybe this also applies to Go. But this is very much not how exceptions (a word these languages don't even use) are understood in languages which have them.
In Java, C#, C++, for example, exceptions are meant for any situation where the operation you asked for can't be completed successfully, for whatever reason. The most common example is IOException: if you asked to read 200 bytes from a socket, but the connection got interrupted after 100 bytes, that's an IOException. This would not be appropriate as a panic in Go or Rust, for a comparison of the difference in philosophy.
> You are explaining the diferrence between error results and panics in Rust
That is indeed an example I included, yes
> In Java, C#, C++, for example, exceptions are meant for any situation where the operation you asked for can't be completed successfully, for whatever reason. The most common example is IOException: if you asked to read 200 bytes from a socket, but the connection got interrupted after 100 bytes, that's an IOException.
- Java has checked exceptions, which... ugh, I'm not going to get too much into this, but this is basically their terrible way of representing expected errors while reusing the try/catch syntax. Yes, the thing is called exceptions at the language level. Yes, they do interrupt control flow. But fundamentally I consider them mere errors.
- C++ overuses exceptions in a similar way, except they're not checked because fuck you, nothing in C++ is checked.
The relevant quote's talking about semantics, so I'm talking about errors and exceptions as something that transcends the individual programming language. Different languages are different and don't always (even usually don't) map these concepts 1:1.
The behavior in Java and C++ is just a different philosophy of what exceptions mean, that is what I was trying to say. The semantics of an exception in these languages is that functions should either return a simple type or throw an exception if they are unable to return that type for whatever reason (be it a bug, an unmet precondition, a temporary error, an unmet postcondition, etc).
Exceptions are not extraordinary events, and it is certainly never appropriate to stop execution because some exception was raised: the program itself is supposed to, at the right level, specify what should happen when an exception is encountered.
The difference between different levels of error, such as fatal errors vs others, is captured by the type of the exception itself.
This all has little to do with Java's checked exceptions support. That is a feature that is supposed to help with verification that the program indeed specifies what to do when an exception happened, for certain classes of errors that are considered more likely to occur in production. C#, which learned from both C++ and Java, doesn't include these, but still has the same semantics for exceptions.
> Exceptions are not extraordinary events, and it is certainly never appropriate to stop execution because some exception was raised: the program itself is supposed to, at the right level, specify what should happen when an exception is encountered.
Exceptions are being used for errors, and that's fine, I just don't consider them actual exceptions because, for example, you expect certain operations to be able to fail and you should handle that failure gracefully. In Rust errors are a separate concept that can be part of the return value, but in Java and C++ (that "different philosophy") errors are also just exceptions and you're expected to know which ones happen in the course of normal operation in order to handle them.
I know in JavaScript I sometimes use try/finally (without catch) for things that throw exceptions that are indeed fatal for my function, and then something higher up can catch errors thrown by that function if it's not quite fatal anymore. But it feels annoying to have to "handle exceptions" when I should be handling expected errors instead, and writing code that contains no unexpected errors. (TypeScript at least makes that easier than it otherwise would be for vanilla JavaScript.)
Again, you're just shoe-horning in your preconceived ideas about how errors should be handled into a different language.
The philosophy of Java and C# especially* is that software should be resilient to errors in production. It doesn't matter if one function for one request with some obscure parameter failed because of a network failure or because of a bug that led to an NPE when that parameter was encountered. What matters is that all other requests that don't have that bug should continue running and not impact users unnecessarily.
So, at some points in my code, I need to handle any possible exception, and decide how to continue from that point. I also need to properly clean up resources and so on regardless of whether I encountered a bug in a subcomponent or an external failure.
Now sure, I probably don't want to be writing "catch (NullPointerException) { ... }", while I may occasionally want "catch (IOException e) {....}" . So there is some difference, but that is where the type system comes in (and the oh-so-hated checked exceptions feature).
* C++'s philosophy is the worse here, of course: programs that have bugs are not valid C++ so they don't even discuss them. Rust's "any bug should crash your program as soon as possible", while still very strange to me, is of course much better.
Java just needs better language constructs for checked exceptions to make them easier to deal with. It seems like the OpenJdk team is finally moving towards looking at making them better though.
I like your example. Although I do remember that there are also read operations where you ask for 200 bytes and you can get 100 back. And it is up to the caller to check for this. Otoh, if the socket is closed you indeed get a socket closed exception. But that is because the docker was closed while reading. What is the rust behaviour in this case?
> I do remember that there are also read operations where you ask for 200 bytes and you can get 100 back.
Those are read operations where you say you have 200 bytes to store the result, rather than where you ask for exactly 200 bytes. But yes, they certainly exist, the ones that ask for exact numbers of bytes are typically built on repeated calls to the ones that can return less.
> Otoh, if the socket is closed you indeed get a socket closed exception. But that is because the docker was closed while reading.
I think this depends on how it closed, sometimes you just get EOF, which is an error state but not an exception (unless your language is clinically insane like Python). Obviously if it's interrupted in such a way that there would probably have been more data but you can no longer get it, that's a different error than EOF (and some languages use exceptional control flow to report mere errors, it is just that Rust does not do this).
> What is the rust behaviour in this case?
Read calls can return less data than you asked for. If they return no data, that is a successful EOS. They can also report I/O errors as distinct from a successful result.
There is an I/O error for "unexpected EOF", where you require more data than is present, and the fact that it's not present is an error. I believe `read_exact` uses this if anything happens before it has entirely filled the buffer, including completely normal connection closure.
In Java, C#, C++, for example, exceptions are meant for any situation where the operation you asked for can't be completed successfully, for whatever reason. The most common example is IOException: if you asked to read 200 bytes from a socket, but the connection got interrupted after 100 bytes, that's an IOException. This would not be appropriate as a panic in Go or Rust, for a comparison of the difference in philosophy.