Insist in what way? The Go website insists that Go has actual exceptions, unlike the pretend exceptions that are actually errors passed around using goto like you find in Java and other languages inspired by it.
Insist in what way? The Go website insists that Go has actual exceptions, unlike the pretend exceptions that are actually errors passed around using goto like you find in Java and other languages inspired by it.
I get that Go devs like to claim they are completely different because you are supposed to use them differently, but under the hood they are identical as far as I can tell.
As to why press a button that does nothing? For the same reason fidget spinners were all the rage a few years back: Bored people like to do something with their hands.
Perhaps if the comment had a question that was left unanswered, people wouldn't have become so bored?
There is more content in the thread, but not about exceptions. So, with that, here's your time to shine!
I thought one difference was that a panic in a goroutine kills the whole process vs. an exception in a Java thread would just kill that thread. That could be more of a consequence of "Goroutines are not threads" rather than "panics are not exceptions".
Java Checked exceptions are certainly quite different than Go panics in terms of compile-time checks and what code the user of the language must write.
I thought there are some differences with how stack traces are accessed on caught/recovered exceptions? It's been a while now but I thought you needed something special to get the Go stack trace out. Fairly minor detail though.
Error is an interface in Go vs. a base class that's extended. Probably more of a result of other language design decisions rather than a decision in this particular area.
I haven't really seen the catch-and-rethrow paradigm for Go panics, but it's kinda different because `panic` only accepts a string argument whereas you can rethrow an "object" in Java (side note - sometimes Go error handling ends up having a lot of string concatenation ex. errors.Wrap because of the focus on "errors are strings"). The lack of catch-and-rethrow is more of a usage difference than a design difference, to your main point.
Probably others but can't think of them right now.
Only if not caught. But in any case this still means that you need to write exception safe code, because you don't know if the function you call will throw, and you don't know if the function calling you will catch.
> `panic` only accepts a string argument
No: https://go.dev/play/p/ZDKpybtxABL
> catch-and-rethrow paradigm for Go panics
Probably because "you're not supposed to". You can.
Do you mean errors.Unwrap? There is no errors.Wrap function.
It does no string concatenation. It merely checks to see if the error value has an Unwrap method and returns the result of that method if available, or nil otherwise.
The only thing that comes close to having anything to do with strings in the error package is errors.New. Even then, the focus is not on the string. It is on the returned value, used when you want to store a 'constant' value for use with errors.Is.
> because of the focus on "errors are strings"
What does that mean? The focus is on types and values that satisfy the error interface. string does not satisfy the error interface. Strings cannot be errors, at least not when errors are passed as an error interface type, as is the convention.
I was referring to this errors.Wrap function: https://pkg.go.dev/github.com/pkg/errors#Wrap
I'm not a Go expert, is this function deprecated/removed?
RE: errors are strings
I probably didn't explain it too well but, for example, you're required to have a string to create an error of course
// errors
func New(message string) error
and then after a bunch of wrapping it ends up coming out like: "get product: fetch user: json unmarshal: invalid data"So it's a bunch of string concatenation. Yes, as you said `error` is not a string it's an interface, but a lot of times it's basically acting like a string that's continuously growing. Rather than something more structured that's composed together, and then later (possibly) serialized to a string.
The "errors" interface mixes the concerns of error tracking with error serializing. I might not need to serialize the error to string because I'm serializing to other formats (protobuf, idk) but I have to use everything that revolves around a string-based API. That's what I meant by "errors are strings", even though that's hyperbole and not literally the case.
Got it. As the github.com identifier implies, that is a third-party library.
I suppose you could argue that said library is focused on strings, but then you could write a similar library in any language. Would you say that would also make those languages focused on strings? Probably not.
I don't think anyone would recommend that you use said library.
> you're required to have a string to create an error of course
It is true that said function does exist in the errors package (the standard library one), but is for defining error 'constants' meant to be evaluated on equality. Consider:
var MyError = errors.New("my error")
if (err == MyError) {
// We got MyError!
}
It is not the string that is significant. It is the comparable memory address that is significant.Note: For reasons, you are bound to want to use errors.Is rather than == in this case, but the intent is the same. I choose to use == here as I think it explains it better.
> Rather than something more structured that's composed together, and then later (possibly) serialized to a string.
No, that's exactly what a Go error is. They are fully-fledged types, able to pack any kind of data you want in them. Consider:
type MyError struct {
Code int
User User
// ...
}
// Error is needed to satisfy the error interface.
func (m MyError) Error() string {
return fmt.Sprintf("code %d caused by %s", m.Code, m.User.Name)
}
func doSomething(user User) error {
return MyError{Code: 1, User: user}
}
That's an error in Go. You are right about strings to the extent that serialization to a string is a requirement of the error interface, but you don't ever need to use it. It is not significant to any meaningful degree. But as logging/reporting is a common way to deal with errors, it is understandable why the serialization is a requirement.Of course, you don't have to use the error interface at all. Some functions in the Go standard library, as an example, use an int type to signify an error. You probably want to use `error` in the common case, though, so that your code plays nice with the expectations of others. Deviation should carry careful consideration.
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).
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?
What compiler failure? Go literally does not warn you until it hits the error at runtime for these exceptions.
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.
Instead, they didn't. And we all suffer for it.
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.
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.
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.
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 _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.
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.
Have you ever developed, operated and maintained a distributed system?
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 couldn't, because that compiler would need to solve the halting problem.
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.
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.
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).
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!
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
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.
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.
My way. The program catches the error, notifies the user, tries to mitigate it and display the contents it can extract to the user.
Your way. Program exits.
> 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!
> 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.
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 Go HTTP server does this internally (though there is talk about not doing it, deferred til Go 2 https://github.com/golang/go/issues/5465).
> 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.
[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.
"Insist in what way": starts with things like the Go FAQ having a question called "Why does Go not have exceptions?".
The answer does elaborate, so it's not like they're lying, exactly. But anything and everything Go says about this also applies to C++. There's no relevant technical difference between Go and C++ exceptions, nor is there a difference in how the standard library uses them.
... except the Go standard library swallows exceptions in some cases, which is like the biggest no-no you can do.
But nobody would say that C++ doesn't have exceptions.
Is it being insisted in some tangential way, like 'Go doesn't have "try", "catch", and "finally" keywords'? Something like that would be true.
Or is the insistence straight up "Go does not have exceptions!"? If that is the case, who is saying it? Did you just read it in one of the regularly scheduled Rust advertisements that get posted here? Or did it come from someone who actually means something in the Go community?
And repeatedly from Pike, core documentation, and other core developers.
Are you referring to the frequently asked question itself? That's the only thing in there that could even possibly make you think Go doesn't have exceptions. Except, being a FAQ, you know that the question comes externally from people who don't know about Go. That is why they are asking the Go people the question! One would have to be braindead to think that is an insistence.
Maybe you need to be even more specific.
Is this some sort of gaslighting? Not only is it in there, I even quoted it for you.
> the question comes externally from people who don't know about Go
And they're told it doesn't have exceptions.
In the last few of years they've started correcting the FAQ and other introductory material.
I'm not going to go through all the intro material again. Maybe in the last 10 years they've fixed the misinformation. But an FAQ answer that doesn't start with "it does, ", is wrong.
Saying "Go doesn't have exceptions", followed by the feature it does have, which is the literal exact definition of exceptions (well, some Lisp languages have fancier exceptions, but still), is gaslighting.
Like I said elsewhere, the C++ standard library also doesn't throw willy-nilly. It's not "not exceptions" just because they boycott that name, or its intended usage.
Just like "Go doesn't have warnings. It's all errors, the equivalent of -Werror" is a lie. They just chose to put the warnings into "go vet", to cover their tracks.
It may be that they've stopped using "doesn't have exceptions" in basically all marketing material now, but when I was learning it a decade ago it was everywhere.
So now we have all this code out there that's not exception safe.
And that's why (to bring it back to the topic) it is a thing that they got wrong.
Exceptions are things that were already broken when you wrote the code (null pointer access, index out of bounds, etc.)
To put it another way, exceptions are failures that a sufficiently smart compiler would have been able to catch at compile time. Of course, creating a compiler that smart is a monumental task, so we accept catching some programmer mistakes at runtime as a reasonable tradeoff.
Would love to see your proof of this for stack overflow exceptions. You could become very famous.
Therefore, a sufficiently smart compiler just needs to ensure that you have done the proper accounting of stack use and handle the error condition if you are approaching an overflow state. With that, you can prevent it becoming an exception.
If you encounter a stack overflow exception, it is because you didn't do your due diligence. Technically your program is flawed. Granted, the pragmatic engineer probably doesn't care about correctness in that area. It turns out not all programs have to be perfect to be useful.
No fame for me, I'm afraid. There is nothing here out of the ordinary.