You might go with Joshua Bloch and say exceptions are also best reserved for exceptional circumstances (which actually only means "things aren't working as expected"), that's why Go's authors used "panic" instead of "throw" or something similar, to make clear that it shouldn't be used where you might use exceptions in other languages. I mean, it's in the FAQ too: https://go.dev/doc/faq#exceptions
When debugging be it C# or JS, neither the "break on all exceptions" or "break on caught exceptions" are useful on any app. One just hits random library shit, or whatever bloat is in the codebase all the time and the other won't break at all.
But because exceptions are the control flow that is the only way to debug them (or do a human binary search)
Not sure what go debugging is like but I imagine you can quickly work your way to the first err!=nil while debugging.
The go equivalent is "catch the exception when it happens here"
A debugger feature for that would be nice. I guess it is a debugger concern not an exceptions issue per se.
> quickly work your way to the first err != nil while debugging
I doubt you'll spend any less time debugging in Go. If you disagree, I'd love to see a "side-by-side" comparison for code that's functionally the same but written in both Go and JS, and see some explanations why it's easier in Go
How do you imagine that happening? I can’t see another way then either stepping through your code or setting breakpoints on all the ‘return nil, err’ statements. You rarely, if ever, can use a ‘watch variable’ feature, because each function will have its own local ‘err’, and will have a new one on each invocation.
If, instead of ‘return buil, err’, there’s ‘throw new…’ in such blocks, I don’t see why you couldn’t do the same things.
Exceptional circumstances, or exceptions for short, mean "things aren't working as expected due to programmer error". In other words, a situation that theoretically could have been avoided by a sufficiently advanced compiler but that wasn't caught until runtime.
"things aren't working as expected" is vague enough to include errors, which are decidedly not exceptions. One might say a hard drive crash or the network failing isn't working as expected, but those situations are not exceptional.
> to make clear that it shouldn't be used where you might use exceptions in other languages.
Other languages are starting to learn that you shouldn't use exception handlers where you wouldn't use panic/recover, so I'm not sure there is a practical difference here.
It is clearly not. It is very much something to anticipate. At sufficient scale, it is guaranteed that it will happen. There is nothing exceptional about it.
> More specific terms is needed to distinguish errors in the code (eg divide by zero) from unpreventable errors like network failure.
Luckily we have such terminology already: Exceptions (caused by mistakes in the code) and errors (caused by external faults).
However, outside of the Java world, which flips the terms around for some reason, this seems to be the prevailing usage. It is also the only usage that makes sense based on what seems to be the prevailing understanding of what "error" and "exceptional" mean. Java's "exceptional conditions" being the ones that you expect to happen regularly doesn't make sense, but was presumably an accident in naming that just so managed to stick.
Of course, confusion is compounded by some language idioms using exception handling constructs to handle errors.
Interesting. In Javaland this describes assertions, and the term exception is for operating errors, i.e. problems not necessarily attributable to programmer error, including your example of a network failure.
Making matters more confusing in Javaland, Errors are separate from Exceptions, but both are Throwables.
An Error is a subclass of Throwable that indicates serious problems that a reasonable application should not try to catch. Most such errors are abnormal conditions. The ThreadDeath error, though a "normal" condition, is also a subclass of Error because most applications should not try to catch it.
https://docs.oracle.com/javase/8/docs/api/java/lang/Error.ht... / https://archive.vn/i5F7B> Which of these are the "checked" exceptions? Throwables are checked exceptions, except if they're also Errors, which are unchecked exceptions, and then there's the Exceptions, which are also Throwables and are the main type of checked exception, except there's one exception to that too, which is that if they are also RuntimeExceptions, because that's the other kind of unchecked exception.
Because I didn't have much time before the final submission, I just put the initial call in a try catch block and threw an exception to indicate successful completion.
The alternative is every single function has to return a boolean, along with whatever else it would return, which is true when you have found a solution, and you then return straight away -- effectively just reimplementing exceptions by hand, which doesn't feel like a useful use of my time.
I love Go, but its error handling leaves so much to be desired. I even have an Emacs macro that inserts "if err != nil" for me. Also very easy to make a mistake in the rare case where you have to write "if err == nil"; my eyes just skip over any line that includes the words "if", "err", and "nil", as if it was some attention-hungry inline ad.
Rust started off chatty, but soon introduced the "?" syntax, which works perfectly well in the 99% case. Go had a couple of similar proposals, but "keep if err != nil" just... won.
Stack traces in other languages do this in a way, but are often unreadable and require manually inspecting each function in the trace to figure out the reason of the bug. Even worse if some function in the middle catches the original exception and returns another exception, completely devoid of the former's context and information.
Even worse if you're using any kind of framework that renders stack traces useless ala async Rust or Java Spring. All you get is a bunch of noise from framework and almost nothing from your program.
In Go, most of the errors I get are chains of carefully written messages, e.g. "request failed: write file: create: directory does not exist". And given that errors are just values and not tied to a stack, they can internally be passed through channels in all kinds of complex goroutine pipelines, and not lose any information.
Go basically makes your life worse, until you get used to writing meaningful context-relevant messages and thinking about errors. Which in turn makes your life a lot easier when diagnozing and debugging an issue.
Not without its related traits and whatnot. Without those you have the same all the same problems the Go proposals keep running into. Trouble is that it is not yet clear what the Go equivalent is to those related features.
However, the attempts to figure that out keep coming. Ian Lance Taylor (of generics fame) recently implemented the latest proposal to try it out, although it seems it too has failed, but suggests that things are inching closer to something usable when the initial details were able stand up to basic scrutiny. Nothing has "won" yet.
(rust, meanwhile, had macros, and people settled on the try! macro fairly quickly, which is basically just bundling up 'if err != nil' into a smaller package, and that eventually turned into '?' because try! was still a bit too unwieldy. Go doesn't have an way to do the same)
addendum: Having read the blog post, it seems like it's basically arguing for interfaces which essentially buffer error reporting. I've written code that works like this before, and it doesn't really deal with control flow well at all: it's basically only a solution for the case of having a straight-line set of function calls, and some interfaces are commonly used like this, but it's only a fraction of them and you introduce more likelihood of errors if you then try to use them in more complicated situations (imagine his proposed option but you want to make a decision based on a value in the middle, or run a loop. It either stops working entirely or you have to decide on a 'safe' fake default value to return in the case of an error fall-through that doesn't break the logic, which is usually impossible.
It’s fine that golang has made missteps along the way and course corrects. Or if people realize that some hoped-for development doesn’t come to pass, but the current state of things is acceptable.
But the number of times it feels like we’re just told things were always this way, or this was always the goal, or actually having 2/3s of the language being redundant boilerplate is a good thing (it’s “explicit”), or actually they always intended to have generics because it’s important for library authors, or actually systems programming always meant small network services, or actually abstraction is bad unless it’s coincidentally the exact level of abstraction that golang ended up with, or… on and on and on.
It’s exhausting and it’s not a good look on the community.
There was a time when generics were not even on the table. I'm pretty sure I remember an old talk by Rob Pike where he says that much.
People can change their mind as they learn more about the problem space; that should be tolerable.
No such development has occurred. The state of the art remains three lines of boilerplate around most function calls in practice.
And honestly, if the mindset was: hey, you know what, this isn’t great but it’s what we’ve ended up with, it isn’t the end of the world, and maybe we’ll crack the nut and improve things down the line… I think a lot of golang’s detractors would nod and everyone would move on.
Instead, everyone clings to: three lines of mostly-identical error handling around every function call is optimal, I prefer it this way, all of this extra code is “explicit” so it’s an inviolable good, we’ve always been at war with Eastasia.
As someone who is more than happy to point out all the things that annoy me about the languages I prefer to use, it feels at times like entire community wants to pretend that there aren’t any flaws or downsides with the language.
> it feels at times like entire community wants to pretend that there aren’t any flaws or downsides with the language.
Yeah, the delusion of version 2.0 perfection is strong from Golang thought leaders. It is nauseating to read about. Like the whole resistance to generics, then finally giving in, then finally saying "Yeah, we definitely need these." So... the last 8(?) years of blogging against generics... what about that? Another comment below used the phrase "gas-lighting the users" which seems appropriate here.Deeper: This will be controversial to some readers here. I wonder if the reason why the "entire community" resists criticism so fiercely is that Rob Pike has a very public profile and "infinite Internet (reputation) points", so no one can beat that final boss. I'm not saying that Rob Pike would use his Internet boss powers to kill off any critics, but I do think it does repress some critics. Also, his style and opinions are very strong.
All said, as a non-Golang writer, but avid reader, I am still very impressed by how carefully that Golang was developed. It could have gone so much worse (C++? JavaScript?), or the community been much more toxic/unwelcoming, e.g., Rust. Overall: It is a huge win for current and future programmers.
What makes you think that? The Go team is clearly working on trying to crack the nut. There is, as of about a month ago, even now an exploratory implementation: https://github.com/golang/go/discussions/71460 It is not an easy nut to crack. Many serious proposals have failed for good reasons. But it is abundantly apparent that the effort is there and ongoing.
The "golang detractors" aren't acting rationally. If you watch carefully you will see that most of the time they don't even understand their detraction points, indicating that they are simply repeating what they saw elsewhere. The unfortunate reality is that they aren't looking to contribute anything meaningful, they are simply trying to be funny and they will continue to believe it is funny until "You idiot, Go already has that" can be said and manages to wear them down.
> three lines of mostly-identical error handling around every function call is optimal
To be fair, while I am sure we could do better, I am not sure we have found it yet. I do my programming in various languages with all these fanciful error handling semantics and it is not good. I regularly wish those languages' idioms had settled on something like if err != nil over what they ended up with. It is not great, but better than the rest. But it is encouraging that smart minds are working hard trying to find better.
The one exception to this trend has been generics. I think it was an inevitable addition to the language, but there are still a lot of holdouts who argue it wasn’t necessary. I think this is probably because it’s more important for library authors than app developers. But it does remain by far the most divisive update to the language.
> The "golang detractors" aren't acting rationally. If you watch carefully you will see that most of the time they don't even understand their detraction points, indicating that they are simply repeating what they saw elsewhere.
I personally don’t think I’ve seen a lot of that. At this point it’s endemic enough that there’s a small and shrinking number of engineers each year who haven’t spent time in it.
> To be fair, while I am sure we could do better, I am not sure we have found it yet.
Everything below here is a more or less reasonable take (even if I have mild disagreements), and I think a lot of people would feel less likely to snipe at the language if more people expressed opinions like this.
One example, if I may: the errors.As function is far from ergonomic. It would have been much better if generics had come to the language before that function was added to the standard library. Modern alternatives exist: https://pkg.go.dev/github.com/jub0bs/errutil
Where is this mantra emanating from, exactly? Go officially became community driven in 2017. If the community didn't seek this, nobody, especially not the core team, would be working on it. Actions certainly do not support your idea.
> there are still a lot of holdouts who argue it wasn’t necessary.
Of course. Obviously it isn't necessary, or even beneficial, for all programming problems. The software industry is quite broad and developers are working on all kinds of different things. One is only going to speak from their own experience. It is all they know. And I expect error handling is contentious in much the same way because of it being something that crosses many different types of programming areas in different ways.
In fact, despite what may seem contrary to what I said earlier, there are classes of of software problems where you do think about errors differently. For example, letting the program close with a message and letting a human deal with whatever went wrong is a perfect solution to many error-related problems and language constructs that make that easy is wonderful. In those cases the fancy error handling semantics are quite appreciated.
Naturally, my assertion that something like "if err != nil" being the best we've got comes from the type of software I normally find myself needing to write. If you work on different types of problems, you're bound to have a different perspective. Expecting to find agreement here is much like expecting basketball players and baseball players to find agreement about what kind of ball they should share. But there is no need to.
It's abundantly clear where the actual Go community stands on this. They have a visible interest in seeing what can be done; to try and find out if there is a better way. That does not guarantee success, but the desire to try is undeniable.
The problem with most languages here is the name "exceptions" implying it's for exceptional scenarios, but without any substitute for good non-local control flow.
Rust: Return Some(value) or None
I used panic() all day, but never recover. I use panic for unrecoverable errors. I thought that's why it's called "panic".
And you are exactly right.
The problem is: People are so used to the "exceptions" paradigm from other languages, when they see "panic-recover" many immediately think "That's the same thing!!"
It isn't, because the only VALID usecase for panics is exactly what you describe: unrecoverable error conditions where terminating the program is the best course of action.
`panic/recover` used like exceptions is an antipattern, and one of the worst code smells in a Go codebase.
Sadly, you need every goroutine to have its own recovery handler. This works well for your general request / task entrypoints, as there should only be one for each kind, but you need to watch out for any third-party libs spawning goroutines without recovery. They will take down your whole server.
Why is you code panic'in? I would let it take down the process and figure out why. I have had backend programs set up to automatically restart, which can be useful. But I would treat any panic as a big deal.
For example, at my work, we have some nightly long running tasks. We don't panic every day. But from time to time, let's say once or twice per month, some code changes cause a panic. On that day, we don't want to kill the long running tasks for no good reason other than somehow indirectly making someone fix the panic. We have alerts for that, and we're all grownups.
Yes it is mutually exclusive. Something that doesn't kill the program, aka a recoverable ERROR CONDITION should not cause a panic, that's not what panics exist for.
Something that causes a panic without using the `panic` keyword, like an out-of-bounds read, nil-derference, etc. is indicative of a serious problem that should be fixed before the program is allowed to run again.
Can you explain why?
> a serious problem that should be fixed before the program is allowed to run again
Can you explain why the program should not be allowed to run again? Is this some sort of software spiritualism?
Because that is semantically what a panic means in Go. See the link to effective go I posted you elsewhere in this thread.
I am well aware that it can be used in other ways. Same as I can say "car" when talking about a mainline battle-tank. Sure, a car has an engine, runs on fuel and drives on land. There are similarities. The words still mean very different things.
And I am also sure there have been instances of someone using a tank to go order food at a drive-through. Doesn't mean that it is semantically correct to do so, or advisable.
https://github.com/search?q=repo%3Agolang%2Fgo%20recover()&t...
It just doesn't make sense to take down the whole server, including all requests / jobs in flight, because there's some nil deref or out-of-bounds. Yea, that thing has to be fixed, but sending a specific alerts is much better than indirectly alerting by taking the whole system down.
If you're using go for something non-web, then it may very well make sense to not have recovery anywhere. Except of course you do have some, in the stdlib. But you can apply it to your code, if you want.
But it can't be some universal pragma (or convention) in go, as it violates the stdlib.
Does any of that change the semantics of what a panic means, and how applications should therefore react? No. Does it make panics the equivalent of exceptions in Python semantically? Also no.
And this logic isn't limited to Go. Guess what, there are python libraries that use Exceptions for control flow. It certainly works. Does that validate using Exceptions as control flow elements? No, of course not. Why? Because that's not what an exception exists for semantically.
Panic-Recover cycles in go codebases are an antipattern, and unless I see an official statement by the people who make Go (who also write "Effective Go" btw.) saying otherwise, those are the semantics of the language.
3P code is a thing
>why
Sometimes there are edge cases with nil pointers that testing missed.
>automatically restart
What about all of the other requests in flight? Letting those fail because one request hit an edge case isn't great for a production service with high throughput.
...and the condition why it panics is not a situation that warrants a crash, then whatever is called upon handling that request is issueing a panic when it shouldn't.
The reason why some libs do that anyway is exactly what I describe above: because in many peoples minds panic == exception.
That's a logic error in the code and should get fixed. And one of the best ways to make devs fix things, is to let their application crash when something that shouldn't happen happens anyway, because then someone will start complaining why a service is unreachable.
TL;DR:
If some condition shouldn't crash a process, it has no earthly business causing a panic.
There will always be panics. You don't need to crash the thing to make devs notice, they're not idiots no matter what Rob Pike told you. You can alert and not throw out the baby with the bathwater. Nobody wants panics in their code, even if they're not crashing the whole world.
I don't think so. If I have to handle a panic, because otherwise my program no longer works, one of 2 things is true in the vast majority of cases:
- There is something seriously wrong with the program or its environment, causing it to panic
- There is something in the program issueing a panic when really it should return an error
In short: there should be no need to "handle panics"
Panics are irrecoverable conditions where its preferable for the program to crash rather than continue. If code panicks for any other reason, thats, in my opinion, wrong, and should be fixed. Panics are not the equivalent to exceptions, and error returns exist for a reason.
People who don't like that paradigm can always use a language that uses the exception-paradigm.
FYI the go std library recovers from panics when it spawns goroutines internally, in most cases.
All this has next to nothing to do with exceptions. Nobody is saying to use panics to pass errors or any control flow.
There is a reason Rust was reluctant to add std::panic::catch_unwind at first. The docs thus explicitly mention that (1) it is not a typical exception mechanism and (2) that it might not even catch panics if unwinding is disabled (common for embedded and restricted development).
For example, I could ignore the fact that Python has exceptions, and instead let functions return error values.
Would that work? Yes, absolutely, and I have seen Py-Codebases that do this.
Is it semantically correct? No, because in python, semantics dictate that error states are handled via exceptions, and that is the expectation everyone has when opening a python codebase.
When in Rome, do as the Romans do.
Result, Either, Expected, all have different names, but their semantics are all the same.
Panic and Recover may not be idiomatically used the same way Exceptions are used in other languages, but they share the exact same semantics of implicitly bailing out potentially multiple functions, going up the call stack until we Catch, or well Recover.
Sometimes it can’t reasonably be handled until some natural boundary. A server that handles multiple connections at once can produce an unrecoverable error handling one of those connections, but it still should gracefully close the connection with the appropriate response. And then not kill the server process itself.
Restarting like that was faster and more stable than crashing the whole thing and restarting the whole server. But it is a bit dangerous if you don't properly clean up your memory (luckily most APIs are stateless besides a database connection)
For example to read and parse expected templates from disk. If they aren't there, there really is no reason to continue, it's just very very confusing.
I would love a community norm that errors which fail the request can just be panics. Unfortunately that's not Go as she is written.
1. Pass a context trace into every function, so that it can panic with richer meaning. That's a right pain very quickly.
2. Return errors, propagating them up the stack with more context:
for i, x := range listOfThings {
y, err := processThing(x)
if err != nil {
return fmt.Errorf("thing %d (%s) failed: %w", i, x, err)
}
}
That said... I did like a clever bit I did where you can use a sentinel error to filter entire segments of the wrapped errors on prod builds. A Dev build gives full error stacks.
What I really want is either a way to recover panics from any goroutine, or be able to install a hook in the runtime which is executed when an unhandled panics occurs.
You can kind of fudge this by having the orchestration layer look at the exit code of the golang process and see if it was exit code 2 which is usually a panic, but I noticed that sometimes the panic stack trace doesn’t make it to the processes log files, most likely due to some weird buffering in stdout/stderr which causes you to lose the trace forever.
For all "unrecoverable panics" you usually want to see the reason, log it, kill the offending process, clean up resources, and then usually restart the offending process.
And that's the reason both Go and Rust ended up reverting their stance on "unrecoverable panics kill your program" and introduced ways to recover from them.
They are more performant because Go decided to make them so. E.g. in Erlang crashing a process is an expected lightweight operation.
As for "easier to understand"... They are not when:
- your code is littered with `x, err = ...; if err != nil`
- it's not easier to understand when the code errors have to be dealt with on a higher/different level. The calling code isn't always the one that needs to deal with all the errors
Just a very random example (I literally just clicked through random files): https://github.com/kubernetes/kubernetes/blob/master/pkg/con...
Oh, look, you can't even see the logic behind all the `if err`s which do nothing but return the error to be handled elsewhere.
Line 143 - 182...
You'd think they'd come up with a short form for something that gets written so often.
return when err
return if err
return on err
And there's high chance the layer above is doing the same thing, and the layer above, until you actually get to actual error handling.
Rust at least recognized this and first provided try? and then the ? operator as a shortcut to this boilerplate
return when err
return if err
return ?> err
return ? err
Still readable, still pretty explicit IMO.Go is just bad at handling errors. Exceptions are superior in every way.
return when err
return if err
return ?> err
return ? err
to: return when stackify(err)
return if stackify(err)
return ?> stackify(err)
return ? stackify(err)
I don't know; I'm just throwing ideas out there.I do agree that exceptions are just fine and it's not like you couldn't just catch and return exceptions if you wanted to like err.
Webserver wants to start, binding port 443/80 isn't possible because another process holds that port.
Logging service wants to write to disk. The IO operation fails.
RDBMS want's to access the persistent storage, the syscall fails due to insufficient permissions.
How are any of those recoverable?
They try to start, cannot do a specific operation, and they do an orderly shutdown. Or they should
Which is exactly what panic does.
Panic is a built-in function that stops the ordinary flow of control and begins panicking... The process continues up the stack until all functions in the current goroutine have returned, at which point the program crashes.
--- end quote ---
This is far from orderly. For example, what happens to other goroutines?
I also like how golang docs literally describe using panics as poor man's exceptions:
--- start quote ---
For a real-world example of panic and recover, see the json package from the Go standard library. It encodes an interface with a set of recursive functions. If an error occurs when traversing the value, panic is called to unwind the stack to the top-level function call, which recovers from the panic and returns an appropriate error value
...
The convention in the Go libraries is that even when a package uses panic internally, its external API still presents explicit error return values.
--- end quote ---
And I like how https://github.com/golang/go/issues/26799 describes that use of panic in the original version of this bog entry from 2010:
quote:
However, this is a poor example and a misuse of panic and recover as exceptions. See https://golang.org/doc/effective_go.html#panic
The example will be invalid soon with the release of go 1.11 as well. The panic/recover was removed from the unmarshal code. See master's
end quote.
The blog entry was later changed because this was fixed. It now refers to marshaling which, sadly, sill uses this mechanism.
The fact that this is still in the json package is a pain point, yes. Does it validate the use of panic as a form of flow control? No. Here is what "Effective Go" has to say about the topic:
https://go.dev/doc/effective_go#panic
quote:
But what if the error is unrecoverable? Sometimes the program simply cannot continue.
For this purpose, there is a built-in function panic that in effect creates a run-time error that will stop the program (but see the next section).
end quote.
And semantically, a panic doesn't even need an orderly shutdown. Again: A panic should ONLY be issued if the application enters a state where continuation of normal operation is not possible, and/or may even be ill advised. "Orderly" operations are, by definition, no longer possible at this point.
> The halting problem is the problem of determining, from a description of an arbitrary computer program and an input, whether the program will finish running
They’re very much related to determining you’re gonna panic or not.
There are only really two types of cases where I would even consider it an option.
Firstly, cases where I am handling an error that should never ever happen AND it is the only error case of the function call such that eliminating it removes the need for an error on the return.
The other case is where I have an existing interface without an error return I need to meet and I have a potential error. This is the result of bad interface design in my opinion but sometimes you have to do what you have to do,
[1] https://github.com/go-chi/chi/blob/master/middleware/recover...
It really depends on the code being written. Try one approach then the other and see if it works better in your situation. For the example in the article there is really no need for an error check in the idiomatic case so why compare that to using panic. If there was an error to check the result would be much different.
In Go you can just ignore it and move on with the zeroed result value. Even the error the compiler gives with unused variables doesn't help since it's likely you've already used the err variable elsewhere in the function.
And there's a few cases like Print() where errors are so commonly ignored you don't even want to use the "_" ignore syntax. Go gives you the poetic license to avoid spamming underscores everywhere. error linters can be configured to handle a variety of strictness. For non-critical software, it's OK to YOLO your Print(). For human-death-on-failure software you may enforce 100% handling, not even allowing explicit ignore "_" (ie become even stricter than Rust language default)
But your editor can give some more realtime feedback instead of waiting for your tests to run, so I guess that's cool.
For example, Go's language guards against unused variables or imports, they are a compiler error. Assigning an `err` variable but not using it is a compiler error. But ignoring the error by assigning it to the reserved underscore variable name is an explicit action by the developer, just like an empty `catch` block in Java/C# or the Rust equivalent.
That is, if you choose to ignore errors, there isn't a language that will stop you. Developers should take responsibility for their own choices, instead of shift blame to the language for making it possible.
Unfortunately, Go's language design also enables unused variables without any error or warning. They are only sometimes a compiler error.
Specifically, multiple return interacts poorly with unused variable detection. See:
func fallable() (int, error) {
return 0, nil
}
func f1() {
val, err := fallable()
if err != nil { panic(err) }
fmt.Println(val)
val2, err := fallable()
fmt.Println(val2)
// notice how I didn't check 'err' this time? This compiles fine
}
When you use `:=` it assigned a new variable, except when you do multiple return it re-assigns existing variables instead of shadowing them, and so the unused variable check considers them as having been used.I've seen so many ignored errors from this poor design choice, so it really does happen in practice.
If developers were more disciplined Go wouldn't need a garbage collector because everyone would just remember to call `free()` when they're done with their memory...
It doesn't force you to do something productive with the error, but you can't act like it was what you wanted instead of an error.
I'm just a simple country gopher, but my brain isn't capable of understanding either of those things, nor are any of my coworkers, nor any of the people we hire, and it doesn't really matter how theoretically "nice" and "pure" your generics and results and monads are if us real professional programmers get confused by them.
Errors need to be explicit values, not magic monads. 'if err != nil' is good and easy to think about, 'res.map().or_else()' is incomprehensible line noise no normal programmer can understand.
https://paulgraham.com/avg.html#:~:text=The%20Blub%20Paradox
The one liner functor stuff in particular I think is a big distraction. To me the basic building block is the match statement - you match on a result, if it's Ok(T) then you get to do something with T, and if it's Err(e) then you get to do something with e. At that level it's very simple. And as far as I know (no expert though) any clever expression can be decomposed into a series of nested and possibly repetitive match statements, the apparent magic is illusory.
But I do hear you and I get the appeal of Go from that angle, obviously sticking with what you like is a good idea. Just saying Rust isn't that bad :)
I definitely have seen that, and sometimes have done that myself, but I have to say it hasn't happened in a while since the linting tools have improved
There is no language to my knowledge that prevent you from ignoring values.
Examples: Austral, some of these https://en.m.wikipedia.org/wiki/Substructural_type_system#Pr... in particular at least ATS, Alms, Granule, LinearML Though most haven't gone beyond research languages yet.
1. Only panic in the top level main() function. Bubble up all errors from sub functions and libraries to the top level function and then decide what to do from there.
2. If you want to offer a library function which can panic on errors, create two versions of the function: one which returns an error, and one which panics on an error and has a name which starts with ’Must’. For example Load() returns an error and MustLoad() doesn’t return an error and instead panics on error.
> So it seems that panic and recover can be beneficial to performance in at least some situations.
Namely top level & centralized recovery handling. I'll also point out its important your panic recovery must happen in a deferred function if your kicking off new goroutines. For example, your server library probably has default panic recovery on the goroutines handling inbound requests, but any new goroutines you create as part of handling the request (e.g. to parallelize work), will not have this handling built in. See https://go.dev/blog/defer-panic-and-recover for more.