Unwind stack, calling cleanup functions in frames that need them, until caught higher up the stacks. Yah, that's exceptions.
Unwind stack, calling cleanup functions in frames that need them, until caught higher up the stacks. Yah, that's exceptions.
The mere fact that they are less used doesn't make them "not exceptions".
And importantly it doesn't matter that they are less common. Merely having exceptions means that everyone has to write exception-safe code.
HTTP handlers silently swallow exceptions, so you can't rely on your program dying if there's a panic.
fmt.Printf can panic as far as you know, too.
Essentially you need RAII, except that because Go doesn't have RAII every single resource needs:
r, err := getResource() if err != nil { … } defer r.Close()
But because "Go doesn't have exceptions" people often don't bother with the defer, and then they get bugs. I see it happening frequently.
You need to write exception-safe code. But also you're not allowed to use exceptions. So it's the worst of both worlds.
I write and review a lot of Go code, and I like the language. But I don't like the dishonesty.
I wasn't aware that this is a real problem in Go land, thanks for the explanation. I wouldn't say the language is "great", was just using it as an example of a modern language where there is a consensus that "they got it right" and not using exceptions, even though error handling is still tedious right now.
I think in Rust you have to consciously fuck up the panic handler to cause similar issues, but I'm not sure.
mu.Lock(); fmt.Print(someoneElsesObject);mu.Unlock();return
You need to get into the habit of writing:
mu.Lock(); defer mu.Unlock(); fmt.Print(someoneElsesObject);return
And this gets extra complicated by the fact that in Go defer runs at end of function, not end of scope. This makes every single for-loop that needs to lock anything hard to read and annoying to write.
for _, a := range stuff { if err:=func()error { mu.Lock();defer mu.Unlock(); return a.stuff()}(); err != nil {return err}}
You can also use defer if you want, but I've never seen it in real code, it's more error-prone and not as flexible. You can build it on top of RAII.
I'm saying "mu.Unlock()" except when deferred, is essentially always a bug. At the very least it's a bug waiting to happen. You need to prove that everything between Lock and Unlock is exception-safe. And that's rarely possible.
As I've said elsewhere you cannot rely on panic triggering program exit, since e.g. HTTP handlers swallow panics.
The fact that you seem to be saying you never see an Unlock deferred proves my point that saying "Go doesn't have exceptions" hurts Go programming. And Go code is in fact full of bugs because there's all this exception-unsafe code.
C++ is naturally exception safe, because RAII. Where it's not exception safe it's because RAII was not used.
Even something as simple as:
mu.Lock()
stats[metricName]++
mu.Unlock()
will throw if there's a path where stats[] map was not inited, and if called in an HTTP handler will leave the lock in place, leading to probably a deadlock of the server. Not great.
let someoneElsesObject = mu.lock();
println!("{}", someoneElsesObject);
return;
when someoneElsesObject goes out of scope, the mutex is unlocked. This happens no matter how it goes out of scope. You cannot forget to do it, because the only way to get access to someoneElsesObject in the first place is locking the mutex, because mutexes wrap the data that they are protecting.https://crates.io/crates/defer exists, but it would be weird to try and use it here, because it can't really be combined directly with this. I guess in theory you could put drop(someoneElsesObject) in the defer block, but like... that already happens for free.
Yeah looks more RAII, and looks like it would not have the problem Go has.
Thanks.
func(){
Code for lock and deferred unlock here
}()
Hence my example where the lambda has to return an error, and the loop has to check for the error. It's A LOT of boilerplate.
Edit: Actually now I don't know what you mean. You clearly replied to my comment that gave a clear example with a return from within the lambda, so what did you think that I didn't know? You took my example and removed extremely commonly needed functionality. So… huh?