Go _doesn't_ have any form of checked exceptions, though, which are required by the compiler to be caught by the caller.
Of course, Go code uses panics much more sparingly than Java or C# or JS or Python use exceptions.
func outer() {
defer catch() {
if r := recover(); r != nil {
fmt.Println("Recovered:", r)
}
}()
// ...
doSomething() // Can panic
doSomethingElse() // Can also panic
}
vs JS: const outer = () => {
try { doSomething() } catch(err) { console.log(err) }
try { doSomethingElse() } catch(err) {
console.error("dang it bobby", err)
throw err
}
}
But in any real Golang situation, you'll be working with libraries or teammates who return (type, error) instead of using panics. You might say this isn't inherent to the language, but it kinda is when all the built-in standard libraries like net/http work this way and the official language style guide tells you to do this. func outer() {
r, err := doSomething()
if err != nil {
fmt.Println("Recovered:", r)
}
r, err := doSomethingElse()
if err != nil {
fmt.Println("Recovered else:", r)
}
}Why not?
func outer() {
func() {
defer catch() {
if r := recover(); r != nil {
fmt.Println("Recovered from doSomething:", r)
}
}()
doSomething() // Can panic
}()
func() {
defer catch() {
if r := recover(); r != nil {
fmt.Println("Recovered from doSomethingElse:", r)
}
}()
doSomethingElse() // Can also panic
}()
}
I don't get why we keep getting these "expert" comments from people who have never used Go before.Hell, if you long for try/catch for some reason, you can even get creative:
func try(fn func(), catch func(any)) {
defer func() {
if r := recover(); r != nil {
catch(r)
}
}()
fn()
}
func outer() {
try(func() {
doSomething()
}, func(r any) {
fmt.Println(r)
})
try(func() {
doSomethingElse()
}, func(r any) {
fmt.Println(r)
})
}
But then you soon start to realize how awful try/catch actually is, so...The recover is still function-wide in your example, which is what I said. You can nest funcs just to deal with this, but it's ugly, and code reviewers won't like it. I use Go on my team, and yeah I'm not as expert as the team who created Go here, but idk why you keep saying I've never used it. My complaint about "if err" is pretty common among Go users, who would understand the joke of calling it "Errlang."
Yes, it suffers all the same problems. And then doesn't even help you with the errors once you get them! You then have to resort to this kind of craziness to do something with the error:
try {
doSomething()
} catch(error) {
if (error instanceof FooError) {
console.log('foo')
} else if (error instanceof BarError) {
console.log('bar')
} else if (error instanceof BazError) {
console.log('baz')
} else {
console.log('unknown')
}
}
Which leaves you wondering why you didn't just write: err := doSomething()
switch {
case errors.Is(err, ErrFoo):
fmt.Println("foo")
case errors.Is(err, ErrBar):
fmt.Println("bar")
case errors.Is(err, ErrBaz):
fmt.Println("baz")
default:
fmt.Println("unknown")
}
At least Java gives you multiple catch blocks to make things slightly more sane.But I get the impression that those who find benefit in passing errors using exception handlers don't handle errors. If you don't have to worry about errors in the code you write, then I think there is a good case to be made that errors as exceptions is a better approach.
That is, after all, the difference between scripts and systems. Scripts can simply fail, leaving the user to try again. Errors as exceptions, or something in the same vein, make sense as the predominant mechanism in scripting languages. Systems, on the other, have to deal with failure. You don't get to just bubble it up and let the user deal with it. This is where errors as exceptions becomes a nightmare. Go is unabashedly a systems language. It is not meant to be a scripting language.
Different tools for different jobs.
They aren't different jobs, though. The two most common uses of JS are backend systems (like you'd often use Golang for) and web frontends, not scripts. Backends will usually catch errors in one place, an HTTP or similar handler, sending back the appropriate status code with maybe a payload. Golang backends do something similar, which is why you see so much "if err != nil... return err" in practice.
Python is more for scripts aka CLIs, but Python backends are fairly common too. And Golang CLIs are also common.
But at least we have a rule checking that you actually did something with the return statuses.
If you want to use a Result/Optional type I think that is way better than exceptions. As long as the compiler can enforce that you are acknowledging (or explicitly ignoring) both the happy and error cases. Ideally the error case for the Result/Optional type would also have an attached stack trace.
Why is it that Go attract so many "experts" who have clearly never used the language even just once?
You might impose it upon yourself when you start to understand the pitfalls of using exceptional handlers for anything other than exceptions, but such is engineering. Everything comes with tradeoffs.
Within a package it can work, but then you'll find language support lacking. For example defer is scoped to a function so you need to write pretty unusual code to make the equivalent of a try block.