Given how they being up how fmt and http swallow them, I believe the parent is referring to panics rather the errors returned via standard control flow.
I guess I'm confused since panics are equally errors passed around by gotos as much as java exceptions are. Probably more so since at least with java it ends up being part of the the function type signature the vast majority of the time.
It creates 2 disparate types of error handling that don't neatly mesh together. You have to handle error return values, but you also have to handle exceptions (panics) because they still exist.
My issue is mostly implementing both ways of bubbling up an error to somewhere it can be handled. I think having either error return values or exceptions is preferable to having both. I don't think exceptions are perfect, but if panic() absolutely has to exist then I'd rather have an entirely exception-based language than a language that uses both systems simultaneously.
E.g. if I write a function that accesses an element of an array without bounds-checking, it could panic and I have to handle that exception. Bounds-checking basically just becomes finding things that would throw exceptions and converting them to errors so we can pretend that exceptions don't exist.
They are disparate conditions. Errors happen in response to conditions that occur during the execution of the application. Exceptions happen in response to conditions that occurred when the code was written. Very different things.
It is highly unlikely that you want to handle an exception. It's the runtime equivalent of a compiler error. Do you also want to handle compiler errors so that your faulty code still compiles? Of course not, so why would you want to do the same when your coding mistakes are noticed at runtime?
There are, uh, exceptions to that when it is necessary to handle exceptions, but if it you see it as routine you're doing something wrong. If you overloaded that with errors, forcing it to be routine, you'd have a nightmare on your hands (like in those other languages that have tried it).
> Exceptions happen in response to conditions that occurred when the code was written.
Huh? Stack overflow? Out of memory?
> It is highly unlikely that you want to handle an exception
It is very likely that I want to handle an exception. In fact, I want to handle all exceptions and keep my process and all other concurrent requests to it running. And don't tell me, that's not possible, because I've been doing that for decades. In Java that is.
Exception. The minimum available stack space is a known quantity. Exceeding it means you made a mistake.
> Out of memory?
Error. The available heap is typically not predictable. Your allocation function should provide an error state; and, indeed, malloc and friends do.
> And don't tell me, that's not possible
It is perfectly possible. Probably not a good idea, though, as you have proven that your code is fundamentally broken. Would you put your code in production if there was a way to ignore compiler failure?
Pick something. I don't care. Let's say failure for reasons of having no return statement in a function that declares itself to return something. If you could flip a switch to see that code still compile somehow, knowing that the program is not correct, would you deploy it to production?
> Go literally does not warn you until it hits the error at runtime for these exceptions.
True, but only because the Go compiler isn't very smart. It trades having a simpler compiler for allowing some programmer faults to not be caught until runtime. But if there was such a thing as an ideal Go compiler, those exceptions would be caught at compile time.
When it comes to exceptions, the fault is in the code itself, unlike errors where the fault is external to the program. Theoretically, those faults could be found before runtime. But it is a really hard problem to solve; hence why we accept exceptions as a practical tradeoff. We are just engineers at the end of the day.
Except we could just treat them the same, and we could have a type system that makes that possible. Multiple languages before Go had a solution to this, that could've been used. Or, it could've written said sufficiently advanced compiler itself.
We could, but we learned from those attempts that came before Go that it is a bad idea. There is good reason why the languages newer than Go, including Rust, also keep a clear division between errors and exceptions.
We already lived through the suffering. The new age of recognizing that exceptions and errors are in no way the same thing and should not be treated as such is a breath of fresh air.
> why the languages newer than Go, including Rust, also keep a clear division between errors and exceptions.
There are also languages older than Go that make this distinction. Java for example. Like Rust (and in contrast to Go) they even have syntax for convenience, and the compiler checks that you handle them.
You know how you can immediately spot old Java APIs and code? The one that was designed and written while we lived through the suffering? Whenever you encounter checked exceptions. Turns out there is no (or even negative) value in this rather arbitrary separation.
Obviously every language can make that distinction. As far as the computer is concerned, errors are no different than email addresses and phone numbers, which are equally representable in every language. Even Java could have errors.
With respect to the bits and bites, exceptions only stand out in that they carry stack trace information. That is useless for email address, phone numbers, and errors. A hard drive crash doesn't happen at line 32. It happens in the hard drive! But knowing that the fault lies at line 32 is very useful for resolving programmer mistakes. If you get a divide by zero exception at line 32, then you know that you missed a conditional on line 31. Exceptions are compiler errors that the compiler missed.
By convention, Java does not acknowledge the existence of errors. It believes that every fault is a programmer mistake, with some programming mistakes needing to be explicitly checked for some reason. Which, of course, questions why you let programmer mistakes ship given that you know that there is a programmer mistake in your code? That doesn't make any sense. But as you point out, everyone these days agrees that idea was nonsensical.
> Java does not acknowledge the existence of errors. It believes that every fault is a programmer mistake
Oh, you're distracted by the stack traces, of which you have a strong opinion, but it's exactly the opposite. What modern Java code does is (in the wake of Exceptional C++) it accepts failure as a given. It does not matter what's the _cause_ of a failure, be it the programmer's fault or failure of a pre-dependend system. It's the job of the dev to ensure that the process can never leave a defined state. And the way to do that is to write exception safe code, and not to handle-all-errors (TM).
Well, there is nothing else. Capturing the stack in a value along with some metadata is all that there is to an exception. Were you wanting me to say something about the weather?
> It does not matter what's the _cause_ of a failure
Except when it does. Let's say the failure is that the user is a child when your requirements demand that they are an adult. age < 18, which produces an error state, doesn't create, let alone raise an exception. Hilariously, you have to resort to Go-style error handling:
if (age < 18) {
// Do something with the failure.
// Are we writing Go now? I thought this was Java.
}
> It's the job of the dev to ensure that the process can never leave a defined state.
If the developer ensures that the process doesn't leave a defined state, then this discussion is moot. You will never encounter an exception. Exceptions are raised when the process enters an undefined state.
> Exceptions are raised when the process enters an undefined state.
Exceptions are raised _before_ a process enters an undefined state. A thread that's unrolling the stack is still in a well defined state.
> age < 18
In real code, the 18 is probably coming from the DB, is the result of resolving the user's location and happens in 5 nested layers of thread pools, logging and transaction management. None of those layers care about age or height of the user. If it's an external API, there is a layer on top that converts some useful exceptions into error codes for the JSON response. Also, there's a catch-all the maps the rest to 500. If it's an internal API, the exception might be serialized in full, to preserve the stack trace across systems.
> Exceptions are raised _before_ a process enters an undefined state.
There is no exception to raise if the state is defined. Why bother? If you know how to divide by zero, just do it! Except you probably don't know how to divide by zero, so you have found yourself in an undefined state and need to bail.
> If it's an external API, there is a layer on top that converts some useful exceptions into error codes for the JSON response.
So you have an exception, that you convert into an error, that you then (on the Java client) handle as if you were writing Go to turn it back into an exception...? I take that you didn't take a moment to read what you wrote? You must have misspoke as that would be the dumbest idea ever.
Of course. And, sadly, one of those systems I once helped with was built on Javascript, which made the whole thing even sillier.
There you had an exception, resorting to as if Go to convert it to an error, handled as if Go to convert back into an exception, and then, when the exception was caught, it was back to 'writing Go' again to figure what the exception was! At least Java does a little better there, I'll give it that.
It is completely ridiculous. I guess that's what happens when you let bootcamp completionists design software.
If a language really wants to embrace the idea that errors and exceptions are the same thing, fine. Maybe it would even prove to be a good idea. But then we should expect that language to actually embrace the idea. This "errors are exceptions, but only sometimes, because we can't figure out how to represent most errors as exceptions" that we see in Java and languages that have taken a similar path is bizarre – and for developers using the language, painful. That has proven to be a bad idea.
They could. The halting problem is solved (well, worked around) by adding a complete type system, which the compiler is free to implement.
But with respect to this discussion the compiler does not need to solve the halting problem. It can assume the program always halts. It makes no difference if the program actually halts or not.
> The minimum available stack space is a known quantity.
It is. Tracking and erroring out on it to avoid the exception means replicating your runtime environment's mechanism for tracking and erroring out on stack overflow (system in a system / inner platform anti-pattern). Your runtime environment's implementors know that, so it's unlikely you'll find the APIs necessary to avoid an exception (i.e. a maxRecursion param and equivalent error result).
> Exceeding it means you made a mistake.
No, it can be just a part of processing a request. Depending on the particular runtime environment, it does not have any impact on other parts of the process.
> so it's unlikely you'll find the APIs necessary to avoid an exception
Lacking a needed API is programmer error. Better programming can avoid that kind of exception. A hypothetical, sufficient smart compiler could fail at compile time, warning you are missing code to handle certain states in the absence of such an API.
To reiterate, exceptions are faults which come as a result of incorrect programs. Errors are faults which come as a result of external conditions. A program that overflows the stack is an incorrect program. The stack size is known in advance. If it is overflown, a programmer didn't do proper accounting and due diligence.
Whoa, easy there. We're talking about standard libraries, and the designers of those are not complete morons. The API is lacking because the runtime environment already provides a safe and defined environment for the observed behavior. It just happens to not fit your mental model, which I find too strict and off wrt reality on one hand, and infeasible on the other (Gödel wants to have a talk with you).
Don't let perfect be the enemy of good. It is quite pragmatic to make such an error.
We're ultimately talking about engineering here. Engineering is all about picking your battles and accepting tradeoffs. You go into it knowing that you will have to settle on making some mistakes. Creating an ideal world is infeasible.
Indeed, it is your mental model that is too strict. To err is fine. To err is human!
> Errors happen in response to conditions that occur during the execution of the application. Exceptions happen in response to conditions that occurred when the code was written.
wat.
You have code that ends up dividing by zero, and boom, you have an exception while the app is running.
> It is highly unlikely that you want to handle an exception.
You always want to handle an exception. That is how actual resilient systems are written
> You have code that ends up dividing by zero, and boom, you have an exception while the app is running.
Yes, and? That problem arose when the code was written. There is no reason why a program should ever find itself in a state where division by zero can occur. A simple if statement is all it takes to avoid that. If you see a divide by zero exception, the developer fucked up; having wrote an incorrect program.
That's entirely different to, say, a hard drive crash causing writes to fail. Not even an ideal programmer writing an ideal piece of software completely void of all defects can avoid an error condition.
> You always want to handle an exception.
No. You always want to ensure that you have no exceptions in the first place. They are the runtime equivalent of compiler errors. If you encounter an exception in your software, you screwed up.
There are circumstances where handling exceptions is warranted, but if you are routinely handling exceptions throughout your development, something is amiss.
Once you get outside webshit you have a world where things need to be able to get the job done even in the presence of software bugs. And software bugs are just another type of failure. Some of those involve cases where failure is expensive or life threatening.
You've got that backwards. When you need to get real shit done you use languages that have a proper type system that eliminates all possibility of exceptions. Given a sufficiently advanced compiler, the absence of exceptions can be proven at compile time.
Letting your program enter into an undefined state where it could wreak all kinds of havoc without anyone realizing beforehand is only acceptable in low-rung "webshit" development.
You can imagine an end user has some structured document created by some other program 10 years ago and they need to look at it. Unfortunately in one place it violated the spec and a field type is wrong. Two ways to deal with it.
My way. The program catches the error, notifies the user, tries to mitigate it and display the contents it can extract to the user.
You might want to read the thread again. You seem confused.
> The program catches the error, notifies the user, tries to mitigate it and display the contents it can extract to the user.
As you will see once you do, that's also my way. Only if you encounter an exception would you exit. As you point out, and to which I agree, your scenario is not exceptional. It is correctly identified as an error. You literally state as such, just as I would.
Again, exiting is reserved for exceptions. Encountering an error is not exceptional. Encountering an error is expected!
I cannot conceive of the scenario where it makes sense to recover a bounds-checking induced panic. The process should crash; the alternative is to continue operating in an unknown, irrecoverable, and potentially security compromised state.
Rust shares Go's "errors as values + panics" philosophy. Rust also has a standard library API for catching panics. Its addition was controversial, but there are two major cases that were specifically enumerated as reasons to add this API: https://github.com/rust-lang/rfcs/blob/master/text/1236-stab...
> It is currently defined as undefined behavior to have a Rust program panic across an FFI boundary. For example if C calls into Rust and Rust panics, then this is undefined behavior. Being able to catch a panic will allow writing C APIs in Rust that do not risk aborting the process they are embedded into.
> Abstractions like thread pools want to catch the panics of tasks being run instead of having the thread torn down (and having to spawn a new thread).
The latter has a few other similar examples, like say, a web server that wants to protect against user code bringing the entire system down.
That said, for various reasons, you don't see catch_unwind used in Rust very often. These are very limited cases.
> I cannot conceive of the scenario where it makes sense to recover a bounds-checking induced panic.
A bog-standard HTTP server (or likely any kind of request-serving daemon). If a client causes a bounds-checking panic, I do not want that to crash the entire server.
It's not even really particular to bounds-checking. If I push a change that causes a nil pointer dereference on a particular handler, I would vastly prefer that it 500's those specific requests rather than crashing the entire server every time it happens.
> The process should crash; the alternative is to continue operating in an unknown, irrecoverable, and potentially security compromised state.
The goroutine should probably crash, but that doesn't necessarily imply that the entire program should crash. For some applications the process and the goroutine are one and the same, but that's not universally true. A lot of applications have some kind of request scope where it's desirable to be able to crash the thread a request is running on without crashing the entire server.
That’s not true. Java has 2 types of exceptions checked and unchecked. Checked exceptions are what this thread has been calling errors, and unchecked exceptions are what this thread has been calling exceptions. Maybe it was a mistake to call them both exceptions, but Java also has 2 types of errors.
I'd say Java named them appropriately. While you are right that they almost cover the same intent, error state is not dependent, whereas checked exceptions force a dependency on the caller[1]. They are not quite the same thing.
[1] Ish. If we are to be pedantic, technically checked exceptions are checked by the exception handlers, not the exceptions themselves. If you return a 'checked' exception rather than throw it, Java won't notice. However, I expect for the purposes of discussion we are including exception handlers under the exception umbrella.
Checked exceptions are NOT like errors-as-values. It's only resemblance is that checked strictly forces the exception to be handled, similar to errors-as-values. But the handling itself is still the same as regular exceptions: out-of-band and not composable with anything else.
Only at the level of Java source. The JVM (and several other languages) doesn’t actually care or enforce which exceptions a method might throw, which is what makes tricks like https://projectlombok.org/features/SneakyThrows possible.