From the book Learning Go by Jon Bodner: "Using recover is recommended in one situation. If you are creating a library for third parties, do not let panics escape the boundaries of your public API. If a panic is possible, a public function should use recover to convert the panic into an error, return it, and let the calling code decide what to do with them."
@@blacklistnr1 It's not just that way for lib creators though. If you write a more complex program you will have more or less isolated parts that may not be 100% bug free and may crash at some point in the future. You may not want your whole program to crash in that case, especially a server. The "public facing" function of such a part of your program should always return an error instead of throwing a panic. The only time you should not recover and let the program crash is if the panic makes the program actually unusable.
IMHO this is a fun side-effect of C ABI being the only way two languages can interop. C doesn't know exceptions so hence everything you expose to an API that can be used by C, it must catch every single exception (or exception-like) that can possibly happen, make sure stack and heap are not clobbered and report the error in a C-like way. Part of the reason why C++ is so unbelievably complex is because it needs to deal with this constantly.
Common use for recover in Go is in web servers/message queue processors. Null references, index out of range panic bugs would cause the entire web server/consumer to crash. You don't want that, especially if its a panic isolated to specific a user/message. So you will typically have a recover in your global middleware somewhere to prevent the panic escalating all the way up the stack and into a crash.
So this isn't completely accurate. Whenever prime would talk about panics in go, he would talk about it as an intended crash behavior. And he's right about all that there probably shouldn't be a way to recover from intended crashes. However, panics can also happen unintentionally for example when trying to dereference a nil pointer, accessing a non existing index in an array, or division by 0. That's something that typically shouldn't happen and it's a core error in the code. That being said, it's probably a good thing that echo (or any other, or even custom) web server has a way to recover from that and not crash the entire server because one handler has some faulty code. It probably is a good thing that you can catch that and possibly do some logging for it and return a 500 rather than crashing the whole program. Another thing worth mentioning is that if go didn't have a method to recover from panics, it could create an attack vector where hackers would try to exploit web servers by creating a panic (many ways to do this) and instead of just getting a 500, they would crash the whole server. This becomes even more likely if you are going tiger beetle and using asserts in production. Recover is also built to work very differently than try/catch. It doesn't return the error type and you can't have different behavior based on what kind of error (panic) happened. It just allows for continuation after a panic happened and then do something about it knowing that a panic happened. I am not saying that it's impossible to use it in a try/catch pattern but it certainly isn't easy to use it in that way. The best way to work with errors in go is as values, as intended. The recover functionality doesn't go against that. Btw love prime and have learned a lot from your videos!!
I honestly don't understand the last part, at least in Java, you throw a new exception (aka panic), then it unwinds the stack, and it recovers if we hit a try while unwinding the stack, the catch does essentially what the defers would do given you remembered to, but if there is no try then the program just crashes. The only difference in behavior here is that Go lets you do the cleanup on the use-site by leveraging defers, but as far I can see this is your run-of-the-mill exception. edit: if I were to point a more clear difference, it would be a cultural one. try-catch isn't discouraged in Java, it is part of the control flow after all, so it means that unlike in Go, people use it willy-nilly without understanding the implications, which leads to the whole set of issues that is nobody doing proper cleanup in the catch (or finally actually, but nobody uses finally)
This! It would otherwise be very har to build any kind fo system that is supposed to continuisly live forever, imagine building a database in go without being able to recover from panics.
@@javierflores09 I suppose you're right. It's mostly the cultural side. Go doesn't encourage the use of try/catch for standard control flow. Also the panic/recover system is very different than the standard try/catch in js and python mostly due to how they are built with different exception types and all that. In theory it's possible to ignore go's intended usage and rely just on try/catch or panic/recover as we'd call it in go but it would be a pretty difficult thing to do unless you intentionally use the errors as values system and explicitly call panic when encountering an error value. In that case, you are going way out of your way to not work with errors as values
@@javierflores09@javierflores09 The difference is that typically a recover is done to translate a panic to a standard error, where the request still fails but fails gracefully, whereas a try/catch is often done to use a fallback and log the error but otherwise continue execution. In theory, yes, you can use a panic in the same way as an exception, you can even have custom panic "types" with the arg to panic(). It any language with whiles and fors, you can use a while to execute a for-style loop, and you can use a for-loop to execute a while. You can also use a string to store an integer, too, and multiply going char by char and matching 0-9. And you can return every value an a class with members of return type and a custom error type. But that's not how those things are designed. Every programming language is equivalent to every other programming language in capabilities, more or less, if you are willing to use features of the language in ways they're not designed to be used. But most people follow the patterns of a language, and those patterns distinguish the features of those languages. If you use panic() in the way exceptions are used in Java, for example, you are using the language wrong.
It basically boils down to these valid use cases: - Use panic if you want your goroutine (and by extension the entire program, thanks for the note) to crash. Don't combine it with a recover. - Use recover if you want to guard against an unexpected crash resulting from a logic error, notably at package boundaries It's panic OR recover. Not panic AND recover.
@@juh9870_projects And with C++ you can also easily make all exceptions call terminate with a single compile flag. So with that logic C++ doesn't have exceptions.
@@juh9870_projects It is used in practice though. Web frameworks often use it to return HTTP 500 errors instead of shutting down when an endpoint handler panics, and there are even libraries like salsa that exploit them for weird stuff (e.g. in salsa for cycle recovery)
I only used recover once, it was for an encoder/decoder that used a lot of reflection. With Go reflection, the compiler won't stop you from doing something stupid, and EVERYTHING panics. Usually I wouldn't recover from implementation mistakes but in context it made sense to put a little defer recover block at the top of my function so I could return an error.
I'd say the big difference is what the stack overflow post said, with try catch, you can run code in a function both when there is an exception and when there isn't. It's just the code directly underneath. With recover, it only runs the code in the defer and then ends the function.
Try = any function that contains or calls code that may panic Catch = the logic after recover Finally = all defers The main upside of gos approach is that you dont need to check what you have to clean up in your "finally", because it is only tacked on as a defer if it worked in the first place. Apart from that, panic AND recover have about the same semantics as throw and catch. But you should ignore those similarities and see it as panic OR recover, as you should only explicitly panic if you want to crash, and you should only recover at package boundaries or if there is a chance of a language-caused panic, such as a divide by zero.
Meanwhile hardcore Python developers, trying to make sense of a two-page try/else statement in a 3rd party library with 65 stars on GitHub that hasn't been updated in 6 years...
The reason it matters that go has exceptions is that even if you don't use them, you *still* have to write exception safe code. E.g. that defer unlock isn't just neat syntax, it's mandatory. And if you say it doesn't have exceptions, people think it's just syntax. Also, printf printers and go standard web server swallows exceptions.
A clarification on echo: recover is not the default behaviour, it's an optional middleware that we can add to the server. So we have the option to let the server crash, or hope that the panic only impacted a single goroutine (i.e. a single request) and has no other side effects. Let's say you have a bug, or a missing validation in your input, which causes the allocation of 8GBs of RAM in a single go, but your machine/container doesn't have that much memory. Do you prefer to crash the entire server, or just that goroutine? Probably it depends case by case.
12:00 🎯 Rust for functions without state, Go on top of that handlining stateful stuffs and communication process (orchestration). It's a good combo. For example: Rust as lambda functions, Go as Event Bus or Message Broker of the functions.
Recover is okay if the code may throw, say, index out of range, and you don't want to crash the entire thing. You catch it, log it, and proceed. Say, a user sent a crafted request that broke some code, you don't want your server to be down for all users
Panic/recover is how you should code a resilient server in go that handles traffic using goroutines. 1. Accept a connection and spin up a goroutine to handle it. 2. At the top of the goroutine stack add a defer/recover to trap panics, log, emit metrics and gracefully return a server error. 3. Then go on and implement all logic using the go errors, never ever use panic. This way if a panic happens (eg out of memory, unmanaged divide by zero or other stuff that panics but doesn't return an error) it is handled gracefully at the top without crashing. All other errors are managed where they happen
The simplest way to understand it is that you recover when two criteria is met: 1. You are calling code that you are not responsible for that might panic 2. Between the defer'ed recover and the end of the defer-ing function, nothing can occur that could the state of that function. Recover acts as a "checkpoint", anything above it in the call stack is the wild west, but anything below must be insulated from it. The web server is the classic example, it can be certain that nothing a handler function can do can disrupt it or corrupt it state, but that code is coming from an outside source, so it defers a recover that handles a panic by logging it, writing a 500 status code, and then cleaning up the connection and going on with its life without impacting other handlers that are running concurrently or preventing new connections from being accepted. On the flipside, there's also basically only one situation in which a panic is justified, and its when you aren't allowed to return an error, but have nothing else valid to return.
The architectural pattern you refer to in 12:13 is called "Pipe and Filters" and it's the one Unix shell CLI tools use (echo | and all that goodness). So you're saying that Rust is really good for writing filters. This is super insightful I'm glad Lex asked that question. I just learned about these things from a 1994 paper "An Introduction to Software Architecture" by Garlan and Shaw. Might be an interesting read for you @prime
So if I understand it correctly: defer is like catch+finally all in one, but written before the throwing code, not after like in other languages, and recover() returns the caught exception, or nil if there's none.
@@3DArea "defer" does what the "finally" block does in C#. My question would be . . . (as someone who hasn't gotten into Go yet), what if you have a "return" statement before a "defer" statement? You said "defer executes always." What if it returns before the end?
That's like saying the Amiga "Guru Meditation" screen and Windows 95 "Blue" screen were Exceptions because the user could continue and "recover" from the panic
About the difference in control flow brought up at 6:29, you can use panic/recover like a try/catch block very easily by wrapping it in a function (5~10 lines), Prime even reacted to an article at some point about someone arriving in a new codebase and finding exactly that 🤢.
A panic in a go routine will crash the entire program. That makes for an extremely poor server implementation. This leads to an interesting design question. Is it better to fail fast or try to be as resilient as possible. I tend to choose the latter.
It depends if a single recover() call can stop the panic, a recover() call followed by a return statement or just a return statement, after which it executes all defer statements up to the panic. There must be some protocol for stopping a panic. If so, then calling panic() again would be equivalent to "rethrowing" an exception.
CLI - crash immediately. GUI - crash if you're not sure you'll recover. Server - give client an error and return from goroutine, main process never crashed. Would love if there'd be a way to crash goroutine without crashing app like in elixir, but gotta work with what we have.
I don’t like panics in Go. That said, I have found panics as flow control helpful for modeling exits in tests. Also, FYI, you can only call `recover()` within deferred code, and `defer` acts a bit like “finally” in more conventional exception languages.
Important to note: recover only works for the CURRENT GOROUTINE, if you panic in a "child" goroutine your defer function will not be called, and you won't be able to recover. The child goroutine will panic, and then the whole program will panic. This makes recovers even less useful imo.
If a goroutine is a separate thread (I got this from a quick web search), then that could happen in any language. You would just have to handle the "error" or "exception" in that thread and find a way to convey the information to the calling thread. You would have the same problem in C# if a thread throws an exception and then terminates . . . I can't remember if, the last time I had to catch an exception in another thread, if it brought down the entire C# program. It's been a while, but this wouldn't be a problem unique to Go. You just have to get your "exception handling" code right.
This is why it pays to have a wrapper around spawning Goroutines that captures panics and wraps them with errors, which when awaited by the parent thread will propagate either as panics or errors.
@@tomsmith6513 It's not technically a thread. It's what called green thread - you just give function to a runtime and it executes it whenever it got a free thread.
@@spl420 it sounds like what happens in .NET (particularly WPF, which I've been using), where you have a dedicated thread processing GUI events. Once it's finished handling one GUI event, it processes the next, on that same thread. As far as I know, .NET threads you create are always somewhere in your application code, until they terminate. They are never left with "nothing to do." It sounds like Go has threads that are idle, not pointing to application code, just waiting for something to do, like a lambda function.
5:15 Go has this for certain things like a concurrent map write or unlocking an unlocked mutex are fatal errors that can't be caught by a recover as opposed to a panic for the exact reason listed for Zig.
Most of the times you cannot fully control other people's code (or even your own code) under a web server. It might be due to nil pointer dereference or just somebody putting panics for things they don't want to handle. It is better to catch the panic, recover and log the error (urging somebody to fix it instead of ignoring it), while other people do not have to experience any unnecessary downtimes.
To get the same behavior as a catch block, you would have to make your panic the last thing in the function. Any code you wanted to run after the potentially panicking code regardless of whether or not it panics (i.e. after the catch block) would need to go in a separate function that was called with defer prior to the panic. It is possible, but unwieldy and not idiomatic.
I'm not a go developer but it seems like recover is just there to do something extra if a panic has occurred. Like, log information to a file before crashing the program. Apparently, panic only runs the deferred statements as it unwinds the stack so it's mostly just running cleanup code. Using recover to resume normal execution from there seems wild and I doubt anyone would actually do that when they could use errors instead.
If one goroutine (thread) panics (including bugs like a null pointer dereference, not just explicit calls to panic()) without recovery, your whole program crashes. Web servers for example usually launch a new goroutine for each individual request then do all processing inside a recover(). If something in the request processing causes a panic, it will be caught and the server can respond with a 500 error instead of killing the whole server process and losing all other in-flight requests. If Go had a way to recover from crashes in another goroutine, you could do clever and awesome things like Erlang (only read about it, never used it myself). What was always controversial was using panic&recover to handle errors in a deeply nested call tree with lots of trivial errors and error propagation, the classic example being a recursive-descent parser. As far as I know, panic/recover works Just Fine there, but is usually considered bad style even if it would save considerable numbers of if (err != nil) return err; lines (upwards of half the code).
I remember trying to understand what errors can a Scanf return, and the way the errors are handled there is not just panics and recovers, but a recover hidden within a deferred function, iirc. So, yeah, Go standard seems to be into that kind of stuff
C# has some like how go handles panic/recover. But the syntax is different. in C# you instantiate a new object with "using" analogous to different. This is syntactic sugar to the compiler which now rewrites it as try { ... } finally { ... }. So there is no catch, but the finally block does cleanup ie call the IDisposable.Dispose() method to ensure cleanup of resources. So the language is expressive enough to allow you avoid using try ... catch (if your shop prefers returning (value, error) tuples) but still benefit from the try .. finally mechanism to ensure resource cleanup before existing the function.
Panic/Recover has always had the same semantics since at least since I've been using go (since 0.9). Recover, also, does not catch all panics. In my experience out of memory errors can not be recovered. I've even ask for it a while back, and the reasoning makes sense. But made it harder to implementing de/serializing things that say they have a set amount of items but the actual number of items is way more, or way less. (rant about how nice it would have been to have an arean alloc here....) The main reason to use recover is for task runners and servers, where you spawn off go routines and they may encounter unrecoverable errors but you don't want to crash the whole program, just crash the go routine, and recover by restarting or logging the issue.
ive run into this in production. pass null characters to programs written in go that dont have a catch all will cause some of them to throw exceptions, when they arent written to catch this as an exception.
I would prefer panic over log.Fatal. because panic can be handled and panic also allows defer calls to run. where as log.fatal will instantly exit the program and does not allow defers to run.
Using go I once had a perfectly legitimate usecase for panic recovery: calling an external parser that sometimes panicked on some invalid input. From my POV all I wanted was: you can't parse that? Just give me an error without exploding.
In a javascript try catch block will run even after it throws the error up the chain of command. I'm pretty sure you can try{}catch(e){throw e}finally{ // do whatever you want }. I know for certain that you can *return* out of a function with a try block and *immediately after* run the finally block.
Panic recover is a pattern when you run something that either succeeds or fails. You mark it as "something that can fail" and when it fails it does not bring you down. This is not an exceptional situation, but a possibility. It panics, you recover, sort of RPC! What panicsis something that your operation does not depend on!!! If you recover from your panic, you recover to an illegal state.
You only recover to an illegal state if the way you choose to use panics is to represent an illegal state. If you use panic/recover just to indicate a function error, then the resulting state is expected. I personally think it’s nice to use panic/assert to always indicate “this should never be the case” and always exit the whole program
in many places panic/recovery is used in the same manner "global exceptions handling" in Java/Spring, or when you need some special transactional logic.
@@wilkesreid If you use panic/recover just to indicate a function error then you have a performance penalty. Apart from the risk that defers do not run.
Actually if a Goroutine panics then the whole process exits. It’s not isolated to the Goroutine. That’s why it’s important to write tests and prevent panics from ever happening.
Note: even a "null ptr dereference" is a panic. We use recover to catch bugs, not expected errors. There are cases where "this API call failed, but the whole server can just panic/recover and then accept new API calls"
@@a6b59ghj51d You are 100% wrong. Derefencing a nil ptr in GoLang causes a panic which can be recovered from. Same with accessing an array out of bounds and failed type assertions.
Conventional wisdom says if you're a web server, recover, don't crash the whole process and take down every other user. But is it as good of an idea as it sounds? In my experience it only breeds complacency and tolerance for runtime exceptions and sloppy code, as well as wearing out the poor on-call staff trying to pick out real high impact issues in a flood of trivial ones. On the other hand I haven't worked with the opposite approach, so I don't know if it's better in reality.
I guess being able to catch panics is crucial for things like webservers. Division operator does not return result, err (and it shouldn't IMO), so it would be stupid to crash the whole HTTP server if division by 0 happens. Yes, you can tigerbeetle it with assertions and add an if check for zero value, but would you do it absolutely everywhere? The divisor can be a result of some other operation, so it's not that simple to do without perf degradation (hot path being branch free etc), so having the option to panic and not crash the whole thing, just one request is nice
It's been two years watching Prime and still for some reason that even i can't find answer to, I keep looking at the one character from start and end that Prime doesn't select. Can someone tell me what is it?
What happens when you call throw without catching it? An unhandled exception error which will crash the program... So create a function alias in Golang called throw which calls panic. Further, what's the difference between logrus.Fatal and logrus.Panic?
"Panic and recovery" it's an oxymoron. Don't do it, certainly don't rely on it. Even if the language supports and promotes it. Stand your ground. Something panicked you SHOULDN"T recover. Scoped exceptions are fine. But I'm so tired of people trying to do things with them, I see it a lot in C# project I need to work with. They use finally like a standard library method.
I use PHP and hate magic __get & __set. I'd rather just call a function, so it's clear that I'm not merely accessing a property and that something is happening under the hood
This is just semantic masturbation from Loris. Go doesn't have exceptions. It's like calling Go dynamically typed language because you could use interface{} everywhere and then use reflect package to assign values later. I mean, nobody stops you from doing that, but it doesn't make it a dynamically typed language! It will be nice if Loris focus more in creating Zig projects and learning materials and less about comparing Zig with other languages over and over.
No, because you do have to do some mental gymnastics to actually reconcile it with exceptions as they are commonly understood in the world of Java, for example. It's not the same. If you watched the rest of the video before commenting you'd have realised!
Oh, if you have a server does 10KRPS and one of your 700 endpoints triggers a panic once, it's maybe not the best idea to crash the entire thing. I mean you could restart the program, but that might take time, it might be misconfigured etc. This way the entire thing stays on the application layer. But if you like to watch the world burn, you do you. Maybe it's better, you reduce all the faulty states you might get into when recovering.
So effectively panic(any) without any recover(void) {...} crashes or is the equiv of [std::]abort(void) | assert(!true) but we can use recover() to "catch" or in this case, recover the "thrown" value returned by a call to panic() to ensure the program does not crash at runtime provided the call to recover() occurs after a call to panic()? (since its deferred in this case)
I sort of get it. The recover() thing is not a "catch." You still have to check the return value of recover(). The advantage of the try-catch block is that you know you have an error. You don't have to check. It's like compile-time type-checking, it's a kind of "software interlocking" that prevents an inconsistent state from happening, where YOU don't need to check for a discrepancy in your code, the language does it FOR YOU. The language itself relieves you of the responsibility of avoiding mistakes. The error-handling philosophy (vs exception handling) suffers from the same problem as C++ templates (in contrast to generics in other languages). It's where the language puts the responsibility on you to get things right. The explicit and verbose error checking at each step introduces multiple points of failure that you have to vigilantly guard against.
Skill issues. In javascript all strings are String() so I don't even have to do anything to get say the 3 letter in a string like ("asmodeus")[2], pretty sure I could even do ("hotdog").split("t").join("es ").
Thats identitcal to Result type in Rust and Either monad in Haskell, i guess Haskell did it first, cause if i recall correctly they were the first language to adopt monads and utilize them for imperative-like syntax sugar
@@idkravitz Not identical to Rust result. Different semantics. I do not have experience with Haskell. Is it having the "other" exceptions too? Because Scala has "them".
@@meryplays8952 well for pure functions the only error types are Optional and Either (or whatever algebraic data type user can define with similar purpose, it's not enforced by language) and also unrecoverable panics (like when some very important assertion fails). In IO Monad there are try and catch functions that, well, does what you can expect them to do: try stops propagation by wrapping result in Either, catch applies recovering function if IO action results in IOError. Panics are rarely used in newer haskell libs, because user can't handle them. And only big brains know what transformer monads use for error handling.
This is not right th-cam.com/video/DYxWnXOn3g8/w-d-xo.html independently whether you use recover within defer, other defers will execute (i.e. to unlock) or even if you "re-throw" with panic inside defer recover function, it will still execute the other defers on the stack!
prime please come back to rust. i just found out that in very very hight traffic, rust beat the shit out of go, handling 2x bigger request per second than go lmao. also one of big company in my country just threw java out and switching to rust :
First language which introduced exceptions was Lisp. IT didn't have "Try" either. Try block was introduced in CLU. So to say go doesn't have exceptions seems like an attempt to rewrite history. I mean you can say go doesn't have compiler either. It's go geter the conqueror of worlds. It just happens to do exactly the same thing compilers do..
Panic is what happens whenever someone looks at my code
recover() often implements opening youtube and watching primagen
How do they recover?
@@takinyash That's the fun part: they don't.
@@oleg4966😂😂😂😂
They don’t recover, they refactor
From the book Learning Go by Jon Bodner: "Using recover is recommended in one situation. If you are creating a library for third parties, do not let panics escape the boundaries of your public API. If a panic is possible, a public function should use recover to convert the panic into an error, return it, and let the calling code decide what to do with them."
Library creators seem to always run into the weird parts of the language, while the rest of us live blissfully unaware of the problems
@@blacklistnr1 It's not just that way for lib creators though. If you write a more complex program you will have more or less isolated parts that may not be 100% bug free and may crash at some point in the future. You may not want your whole program to crash in that case, especially a server. The "public facing" function of such a part of your program should always return an error instead of throwing a panic. The only time you should not recover and let the program crash is if the panic makes the program actually unusable.
IMHO this is a fun side-effect of C ABI being the only way two languages can interop. C doesn't know exceptions so hence everything you expose to an API that can be used by C, it must catch every single exception (or exception-like) that can possibly happen, make sure stack and heap are not clobbered and report the error in a C-like way. Part of the reason why C++ is so unbelievably complex is because it needs to deal with this constantly.
This is all because you cant recover from a go routine your code didn’t start it.
Thank you!
Common use for recover in Go is in web servers/message queue processors. Null references, index out of range panic bugs would cause the entire web server/consumer to crash. You don't want that, especially if its a panic isolated to specific a user/message. So you will typically have a recover in your global middleware somewhere to prevent the panic escalating all the way up the stack and into a crash.
So this isn't completely accurate. Whenever prime would talk about panics in go, he would talk about it as an intended crash behavior. And he's right about all that there probably shouldn't be a way to recover from intended crashes. However, panics can also happen unintentionally for example when trying to dereference a nil pointer, accessing a non existing index in an array, or division by 0. That's something that typically shouldn't happen and it's a core error in the code. That being said, it's probably a good thing that echo (or any other, or even custom) web server has a way to recover from that and not crash the entire server because one handler has some faulty code. It probably is a good thing that you can catch that and possibly do some logging for it and return a 500 rather than crashing the whole program.
Another thing worth mentioning is that if go didn't have a method to recover from panics, it could create an attack vector where hackers would try to exploit web servers by creating a panic (many ways to do this) and instead of just getting a 500, they would crash the whole server. This becomes even more likely if you are going tiger beetle and using asserts in production.
Recover is also built to work very differently than try/catch. It doesn't return the error type and you can't have different behavior based on what kind of error (panic) happened. It just allows for continuation after a panic happened and then do something about it knowing that a panic happened. I am not saying that it's impossible to use it in a try/catch pattern but it certainly isn't easy to use it in that way. The best way to work with errors in go is as values, as intended. The recover functionality doesn't go against that.
Btw love prime and have learned a lot from your videos!!
I honestly don't understand the last part, at least in Java, you throw a new exception (aka panic), then it unwinds the stack, and it recovers if we hit a try while unwinding the stack, the catch does essentially what the defers would do given you remembered to, but if there is no try then the program just crashes. The only difference in behavior here is that Go lets you do the cleanup on the use-site by leveraging defers, but as far I can see this is your run-of-the-mill exception.
edit: if I were to point a more clear difference, it would be a cultural one. try-catch isn't discouraged in Java, it is part of the control flow after all, so it means that unlike in Go, people use it willy-nilly without understanding the implications, which leads to the whole set of issues that is nobody doing proper cleanup in the catch (or finally actually, but nobody uses finally)
This! It would otherwise be very har to build any kind fo system that is supposed to continuisly live forever, imagine building a database in go without being able to recover from panics.
@@javierflores09 I suppose you're right. It's mostly the cultural side. Go doesn't encourage the use of try/catch for standard control flow. Also the panic/recover system is very different than the standard try/catch in js and python mostly due to how they are built with different exception types and all that.
In theory it's possible to ignore go's intended usage and rely just on try/catch or panic/recover as we'd call it in go but it would be a pretty difficult thing to do unless you intentionally use the errors as values system and explicitly call panic when encountering an error value. In that case, you are going way out of your way to not work with errors as values
@@javierflores09@javierflores09 The difference is that typically a recover is done to translate a panic to a standard error, where the request still fails but fails gracefully, whereas a try/catch is often done to use a fallback and log the error but otherwise continue execution.
In theory, yes, you can use a panic in the same way as an exception, you can even have custom panic "types" with the arg to panic(). It any language with whiles and fors, you can use a while to execute a for-style loop, and you can use a for-loop to execute a while. You can also use a string to store an integer, too, and multiply going char by char and matching 0-9. And you can return every value an a class with members of return type and a custom error type. But that's not how those things are designed. Every programming language is equivalent to every other programming language in capabilities, more or less, if you are willing to use features of the language in ways they're not designed to be used. But most people follow the patterns of a language, and those patterns distinguish the features of those languages. If you use panic() in the way exceptions are used in Java, for example, you are using the language wrong.
It basically boils down to these valid use cases:
- Use panic if you want your goroutine (and by extension the entire program, thanks for the note) to crash. Don't combine it with a recover.
- Use recover if you want to guard against an unexpected crash resulting from a logic error, notably at package boundaries
It's panic OR recover. Not panic AND recover.
rust has exceptions too. you can catch panics
Shhhhhh! Don't let them know
Rust doesn't guarantee any panic would get caught, making it unreliable in practice and more of a debug tool, and you can easily make all panics abort
I can also catch panics irl, it's not fun
@@juh9870_projects And with C++ you can also easily make all exceptions call terminate with a single compile flag. So with that logic C++ doesn't have exceptions.
@@juh9870_projects It is used in practice though. Web frameworks often use it to return HTTP 500 errors instead of shutting down when an endpoint handler panics, and there are even libraries like salsa that exploit them for weird stuff (e.g. in salsa for cycle recovery)
I only used recover once, it was for an encoder/decoder that used a lot of reflection. With Go reflection, the compiler won't stop you from doing something stupid, and EVERYTHING panics. Usually I wouldn't recover from implementation mistakes but in context it made sense to put a little defer recover block at the top of my function so I could return an error.
I'd say the big difference is what the stack overflow post said, with try catch, you can run code in a function both when there is an exception and when there isn't. It's just the code directly underneath.
With recover, it only runs the code in the defer and then ends the function.
It will end the function and run all defers.
Try = any function that contains or calls code that may panic
Catch = the logic after recover
Finally = all defers
The main upside of gos approach is that you dont need to check what you have to clean up in your "finally", because it is only tacked on as a defer if it worked in the first place. Apart from that, panic AND recover have about the same semantics as throw and catch. But you should ignore those similarities and see it as panic OR recover, as you should only explicitly panic if you want to crash, and you should only recover at package boundaries or if there is a chance of a language-caused panic, such as a divide by zero.
Meanwhile hardcore Python developers, trying to make sense of a two-page try/else statement in a 3rd party library with 65 stars on GitHub that hasn't been updated in 6 years...
That’s part of the fun 😂
The reason it matters that go has exceptions is that even if you don't use them, you *still* have to write exception safe code.
E.g. that defer unlock isn't just neat syntax, it's mandatory.
And if you say it doesn't have exceptions, people think it's just syntax.
Also, printf printers and go standard web server swallows exceptions.
A clarification on echo: recover is not the default behaviour, it's an optional middleware that we can add to the server. So we have the option to let the server crash, or hope that the panic only impacted a single goroutine (i.e. a single request) and has no other side effects.
Let's say you have a bug, or a missing validation in your input, which causes the allocation of 8GBs of RAM in a single go, but your machine/container doesn't have that much memory. Do you prefer to crash the entire server, or just that goroutine? Probably it depends case by case.
12:00 🎯 Rust for functions without state, Go on top of that handlining stateful stuffs and communication process (orchestration). It's a good combo. For example: Rust as lambda functions, Go as Event Bus or Message Broker of the functions.
The name: "I wanna see Lex and Prime in a stream together"
Recover is okay if the code may throw, say, index out of range, and you don't want to crash the entire thing. You catch it, log it, and proceed. Say, a user sent a crafted request that broke some code, you don't want your server to be down for all users
Panic/recover is how you should code a resilient server in go that handles traffic using goroutines.
1. Accept a connection and spin up a goroutine to handle it.
2. At the top of the goroutine stack add a defer/recover to trap panics, log, emit metrics and gracefully return a server error.
3. Then go on and implement all logic using the go errors, never ever use panic.
This way if a panic happens (eg out of memory, unmanaged divide by zero or other stuff that panics but doesn't return an error) it is handled gracefully at the top without crashing. All other errors are managed where they happen
The simplest way to understand it is that you recover when two criteria is met:
1. You are calling code that you are not responsible for that might panic
2. Between the defer'ed recover and the end of the defer-ing function, nothing can occur that could the state of that function.
Recover acts as a "checkpoint", anything above it in the call stack is the wild west, but anything below must be insulated from it. The web server is the classic example, it can be certain that nothing a handler function can do can disrupt it or corrupt it state, but that code is coming from an outside source, so it defers a recover that handles a panic by logging it, writing a 500 status code, and then cleaning up the connection and going on with its life without impacting other handlers that are running concurrently or preventing new connections from being accepted.
On the flipside, there's also basically only one situation in which a panic is justified, and its when you aren't allowed to return an error, but have nothing else valid to return.
The architectural pattern you refer to in 12:13 is called "Pipe and Filters" and it's the one Unix shell CLI tools use (echo | and all that goodness). So you're saying that Rust is really good for writing filters. This is super insightful I'm glad Lex asked that question. I just learned about these things from a 1994 paper "An Introduction to Software Architecture" by Garlan and Shaw. Might be an interesting read for you @prime
So if I understand it correctly: defer is like catch+finally all in one, but written before the throwing code, not after like in other languages, and recover() returns the caught exception, or nil if there's none.
defer executes always in go (good or bad behaviour), catch in other languages doesn't. You can panic() and a defer will execute as well
@@3DArea "defer" does what the "finally" block does in C#. My question would be . . . (as someone who hasn't gotten into Go yet), what if you have a "return" statement before a "defer" statement? You said "defer executes always." What if it returns before the end?
@@tomsmith6513 Statements execute in lexical order. If an early return is executed prior to a defer then that defer won't be executed.
@@jack-d2e6i Yes, except if you have multiple defer statements, then they are executed in reverse first in first out principle.
Well, basically deffer is just finally block, but for a whole function, and you can have multiple deffer's in one function
That's like saying the Amiga "Guru Meditation" screen and Windows 95 "Blue" screen were Exceptions because the user could continue and "recover" from the panic
About the difference in control flow brought up at 6:29, you can use panic/recover like a try/catch block very easily by wrapping it in a function (5~10 lines), Prime even reacted to an article at some point about someone arriving in a new codebase and finding exactly that 🤢.
It's nothing like try catch, recover only exists if you can save the panic.
A panic in a go routine will crash the entire program. That makes for an extremely poor server implementation. This leads to an interesting design question. Is it better to fail fast or try to be as resilient as possible. I tend to choose the latter.
Crash fast but locally. Make some boundary
It depends if a single recover() call can stop the panic, a recover() call followed by a return statement or just a return statement, after which it executes all defer statements up to the panic. There must be some protocol for stopping a panic.
If so, then calling panic() again would be equivalent to "rethrowing" an exception.
that's basically the prime use case for 'recover'
Fail fast, like erlang.
CLI - crash immediately.
GUI - crash if you're not sure you'll recover.
Server - give client an error and return from goroutine, main process never crashed. Would love if there'd be a way to crash goroutine without crashing app like in elixir, but gotta work with what we have.
Highlight the first and last letters already, I can't unsee it now.
Whoa! This is the first time I see Prime is reading TH-cam chat!
I don’t like panics in Go.
That said, I have found panics as flow control helpful for modeling exits in tests.
Also, FYI, you can only call `recover()` within deferred code, and `defer` acts a bit like “finally” in more conventional exception languages.
Important to note: recover only works for the CURRENT GOROUTINE, if you panic in a "child" goroutine your defer function will not be called, and you won't be able to recover. The child goroutine will panic, and then the whole program will panic. This makes recovers even less useful imo.
btw Rust panics and panic propagation work very differently afaik
If a goroutine is a separate thread (I got this from a quick web search), then that could happen in any language. You would just have to handle the "error" or "exception" in that thread and find a way to convey the information to the calling thread. You would have the same problem in C# if a thread throws an exception and then terminates . . . I can't remember if, the last time I had to catch an exception in another thread, if it brought down the entire C# program. It's been a while, but this wouldn't be a problem unique to Go. You just have to get your "exception handling" code right.
This is why it pays to have a wrapper around spawning Goroutines that captures panics and wraps them with errors, which when awaited by the parent thread will propagate either as panics or errors.
@@tomsmith6513 It's not technically a thread. It's what called green thread - you just give function to a runtime and it executes it whenever it got a free thread.
@@spl420 it sounds like what happens in .NET (particularly WPF, which I've been using), where you have a dedicated thread processing GUI events. Once it's finished handling one GUI event, it processes the next, on that same thread.
As far as I know, .NET threads you create are always somewhere in your application code, until they terminate. They are never left with "nothing to do."
It sounds like Go has threads that are idle, not pointing to application code, just waiting for something to do, like a lambda function.
It would be fun to see a reaction to the "Land of Lisp" music video.
5:15 Go has this for certain things like a concurrent map write or unlocking an unlocked mutex are fatal errors that can't be caught by a recover as opposed to a panic for the exact reason listed for Zig.
Most of the times you cannot fully control other people's code (or even your own code) under a web server. It might be due to nil pointer dereference or just somebody putting panics for things they don't want to handle. It is better to catch the panic, recover and log the error (urging somebody to fix it instead of ignoring it), while other people do not have to experience any unnecessary downtimes.
What is a panic if not an unhandled exception in the program?
If the exception is unhandled of course a crash happens.
"We'll stop panicking later" sounds scarier than "we'll handle the exception later"?
Prime, you should do some Odin
To get the same behavior as a catch block, you would have to make your panic the last thing in the function. Any code you wanted to run after the potentially panicking code regardless of whether or not it panics (i.e. after the catch block) would need to go in a separate function that was called with defer prior to the panic. It is possible, but unwieldy and not idiomatic.
9:09 you could think of it as Go' `try` is always implicit
I'm not a go developer but it seems like recover is just there to do something extra if a panic has occurred. Like, log information to a file before crashing the program. Apparently, panic only runs the deferred statements as it unwinds the stack so it's mostly just running cleanup code. Using recover to resume normal execution from there seems wild and I doubt anyone would actually do that when they could use errors instead.
If one goroutine (thread) panics (including bugs like a null pointer dereference, not just explicit calls to panic()) without recovery, your whole program crashes. Web servers for example usually launch a new goroutine for each individual request then do all processing inside a recover(). If something in the request processing causes a panic, it will be caught and the server can respond with a 500 error instead of killing the whole server process and losing all other in-flight requests. If Go had a way to recover from crashes in another goroutine, you could do clever and awesome things like Erlang (only read about it, never used it myself).
What was always controversial was using panic&recover to handle errors in a deeply nested call tree with lots of trivial errors and error propagation, the classic example being a recursive-descent parser. As far as I know, panic/recover works Just Fine there, but is usually considered bad style even if it would save considerable numbers of if (err != nil) return err; lines (upwards of half the code).
Recovers used that way, for example in CGo projects you gotta use it for C interop. Also having recover in library to return errors isn't uncommon.
Wow. Really cool getting a notification for a comment that now appears to be hidden... Gotta rub it in your face, don't they?
I remember trying to understand what errors can a Scanf return, and the way the errors are handled there is not just panics and recovers, but a recover hidden within a deferred function, iirc. So, yeah, Go standard seems to be into that kind of stuff
C# has some like how go handles panic/recover. But the syntax is different.
in C# you instantiate a new object with "using" analogous to different. This is syntactic sugar to the compiler which now rewrites it as try { ... } finally { ... }. So there is no catch, but the finally block does cleanup ie call the IDisposable.Dispose() method to ensure cleanup of resources.
So the language is expressive enough to allow you avoid using try ... catch (if your shop prefers returning (value, error) tuples) but still benefit from the try .. finally mechanism to ensure resource cleanup before existing the function.
X-prime- on the Hub now!
in my codebases a panic is "I cannot recover so I just need to crash"
Panic/Recover has always had the same semantics since at least since I've been using go (since 0.9). Recover, also, does not catch all panics. In my experience out of memory errors can not be recovered. I've even ask for it a while back, and the reasoning makes sense. But made it harder to implementing de/serializing things that say they have a set amount of items but the actual number of items is way more, or way less. (rant about how nice it would have been to have an arean alloc here....) The main reason to use recover is for task runners and servers, where you spawn off go routines and they may encounter unrecoverable errors but you don't want to crash the whole program, just crash the go routine, and recover by restarting or logging the issue.
I suppose with "Rust book on ownership" he's referring to Chapter 4: Understanding Ownership from The Rust Programming Language
Forever lockalock.
Remember, you can't crash if you wrap your entire program in an exception.
Just to remember... recover catches nil pointer deference, not just panic()s.
ive run into this in production. pass null characters to programs written in go that dont have a catch all will cause some of them to throw exceptions, when they arent written to catch this as an exception.
panics in golang is a pain
Oh no, go has exceptions, time to panic. We might recover though.
I would prefer panic over log.Fatal. because panic can be handled and panic also allows defer calls to run. where as log.fatal will instantly exit the program and does not allow defers to run.
7:01 - That's wrong. defer is exactly the same as try-finally, but reordered so that it doesn't increase indentation level of whole code (good).
Go error = Java checked exception (environment error)
Go panic = Java runtime exception (programming or hardware error (i.e. logical error))
Using go I once had a perfectly legitimate usecase for panic recovery: calling an external parser that sometimes panicked on some invalid input. From my POV all I wanted was: you can't parse that? Just give me an error without exploding.
In a javascript try catch block will run even after it throws the error up the chain of command. I'm pretty sure you can try{}catch(e){throw e}finally{ // do whatever you want }. I know for certain that you can *return* out of a function with a try block and *immediately after* run the finally block.
Panic recover is a pattern when you run something that either succeeds or fails. You mark it as "something that can fail" and when it fails it does not bring you down. This is not an exceptional situation, but a possibility. It panics, you recover, sort of RPC! What panicsis something that your operation does not depend on!!! If you recover from your panic, you recover to an illegal state.
You only recover to an illegal state if the way you choose to use panics is to represent an illegal state. If you use panic/recover just to indicate a function error, then the resulting state is expected. I personally think it’s nice to use panic/assert to always indicate “this should never be the case” and always exit the whole program
in many places panic/recovery is used in the same manner "global exceptions handling" in Java/Spring, or when you need some special transactional logic.
@@JulianBG I agree, but this is not the only use case, there is room for "other" uses.
@@wilkesreid If you use panic/recover just to indicate a function error then you have a performance penalty. Apart from the risk that defers do not run.
panic/recover feels more like trapping SIGSEGV in C.
if the panic came from a library I use maybe I want to handle it, recover the panic is the only way to manage it without restart the program
Actually if a Goroutine panics then the whole process exits. It’s not isolated to the Goroutine. That’s why it’s important to write tests and prevent panics from ever happening.
I think recovering panics is ok on thread or tasks boundaries. Also on a server I think recovering and sending a 500 is ok.
Note: even a "null ptr dereference" is a panic. We use recover to catch bugs, not expected errors. There are cases where "this API call failed, but the whole server can just panic/recover and then accept new API calls"
Incorrect. That's a segfault which can't be recovered unlike a panic which is simply an Unhandled Exception
@@a6b59ghj51d No. You are 100% wrong. Go and try it.
@@a6b59ghj51d You are 100% wrong. Derefencing a nil ptr in GoLang causes a panic which can be recovered from. Same with accessing an array out of bounds and failed type assertions.
Conventional wisdom says if you're a web server, recover, don't crash the whole process and take down every other user. But is it as good of an idea as it sounds? In my experience it only breeds complacency and tolerance for runtime exceptions and sloppy code, as well as wearing out the poor on-call staff trying to pick out real high impact issues in a flood of trivial ones.
On the other hand I haven't worked with the opposite approach, so I don't know if it's better in reality.
If Go is so good, why isn't there a GOTO?
Our go API recovers if the function panics so it doesn't bring the whole server down. Makes sense to me.
They are just copying C#... just accept that C# is the best and move on.
I accept that you're mentally impaired
Modern programmers are absolute insane.
Changing the name does not change the behavior
I guess being able to catch panics is crucial for things like webservers. Division operator does not return result, err (and it shouldn't IMO), so it would be stupid to crash the whole HTTP server if division by 0 happens. Yes, you can tigerbeetle it with assertions and add an if check for zero value, but would you do it absolutely everywhere? The divisor can be a result of some other operation, so it's not that simple to do without perf degradation (hot path being branch free etc), so having the option to panic and not crash the whole thing, just one request is nice
It's been two years watching Prime and still for some reason that even i can't find answer to, I keep looking at the one character from start and end that Prime doesn't select.
Can someone tell me what is it?
00:38 calling function with no arguments and accessing variable is exact same syntax in pascal btw
What happens when you call throw without catching it? An unhandled exception error which will crash the program... So create a function alias in Golang called throw which calls panic. Further, what's the difference between logrus.Fatal and logrus.Panic?
"Panic and recovery" it's an oxymoron. Don't do it, certainly don't rely on it. Even if the language supports and promotes it. Stand your ground. Something panicked you SHOULDN"T recover.
Scoped exceptions are fine. But I'm so tired of people trying to do things with them, I see it a lot in C# project I need to work with. They use finally like a standard library method.
Yes...yes it does.
I use PHP and hate magic __get & __set. I'd rather just call a function, so it's clear that I'm not merely accessing a property and that something is happening under the hood
on the other hand it's sometimes useful to be able to change representation without breaking everything downstream
but that's the only sane usecase
Hi, Prime. What do you think about expulsion of Russian maintainers situation in the Linux kernel?
This is just semantic masturbation from Loris. Go doesn't have exceptions. It's like calling Go dynamically typed language because you could use interface{} everywhere and then use reflect package to assign values later. I mean, nobody stops you from doing that, but it doesn't make it a dynamically typed language!
It will be nice if Loris focus more in creating Zig projects and learning materials and less about comparing Zig with other languages over and over.
Hard agree
Rust has exceptions too then, right? Catch unwind anyone?
So golang panic is more like longjmp/setjmp...amirite?
obviously. This is obvious as soon as you hear about recover.
No, because you do have to do some mental gymnastics to actually reconcile it with exceptions as they are commonly understood in the world of Java, for example. It's not the same. If you watched the rest of the video before commenting you'd have realised!
Oh, if you have a server does 10KRPS and one of your 700 endpoints triggers a panic once, it's maybe not the best idea to crash the entire thing. I mean you could restart the program, but that might take time, it might be misconfigured etc. This way the entire thing stays on the application layer.
But if you like to watch the world burn, you do you. Maybe it's better, you reduce all the faulty states you might get into when recovering.
Terrifying!!! I have seen an open source "codebase" that uses this because this is how M$$$$ does it with SEH!!!
Wait 'til you see the error recovery in ANSI Common Lisp.
Was this intended to make Go programmers feel good or bad ?
D looks interesting.
D eeez nuts
This feels more a like a sql error trap.
It's actually not used in the parsing code anymore
So effectively panic(any) without any recover(void) {...} crashes or is the equiv of [std::]abort(void) | assert(!true) but we can use recover() to "catch" or in this case, recover the "thrown" value returned by a call to panic() to ensure the program does not crash at runtime provided the call to recover() occurs after a call to panic()? (since its deferred in this case)
Bust Rust could also recover from panics lul jesus
Blind people
I just got clickbaited.
Go exceptions are simple, there's no "catch" :)
I sort of get it. The recover() thing is not a "catch." You still have to check the return value of recover().
The advantage of the try-catch block is that you know you have an error. You don't have to check. It's like compile-time type-checking, it's a kind of "software interlocking" that prevents an inconsistent state from happening, where YOU don't need to check for a discrepancy in your code, the language does it FOR YOU. The language itself relieves you of the responsibility of avoiding mistakes.
The error-handling philosophy (vs exception handling) suffers from the same problem as C++ templates (in contrast to generics in other languages). It's where the language puts the responsibility on you to get things right.
The explicit and verbose error checking at each step introduces multiple points of failure that you have to vigilantly guard against.
11:00 Go regexes don't even have negative lookaheads
Skill issues. In javascript all strings are String() so I don't even have to do anything to get say the 3 letter in a string like ("asmodeus")[2], pretty sure I could even do ("hotdog").split("t").join("es ").
Nice to see Russian shill Lex Fridman in the chat!
Omg, a Prime and Lex podcast ep coming ? That would be HUGE, Lex, sir, please make it happen!
Zig exceptions should be THE DEFINITION of exceptions. The rest are dangerous erroneous implementations of algebraic effects.
Thats identitcal to Result type in Rust and Either monad in Haskell, i guess Haskell did it first, cause if i recall correctly they were the first language to adopt monads and utilize them for imperative-like syntax sugar
@@idkravitz Not identical to Rust result. Different semantics. I do not have experience with Haskell. Is it having the "other" exceptions too? Because Scala has "them".
@@meryplays8952 well for pure functions the only error types are Optional and Either (or whatever algebraic data type user can define with similar purpose, it's not enforced by language) and also unrecoverable panics (like when some very important assertion fails). In IO Monad there are try and catch functions that, well, does what you can expect them to do: try stops propagation by wrapping result in Either, catch applies recovering function if IO action results in IOError. Panics are rarely used in newer haskell libs, because user can't handle them. And only big brains know what transformer monads use for error handling.
I knew it! Zig is wke too!
Alas, what isn't nowadays
D MENTIONED!
wrt = with respect to
Perhaps it is time we invert the control flow and stop with this error handling madness
One view and 50 seconds? Prime fell off.
This is not right th-cam.com/video/DYxWnXOn3g8/w-d-xo.html independently whether you use recover within defer, other defers will execute (i.e. to unlock) or even if you "re-throw" with panic inside defer recover function, it will still execute the other defers on the stack!
prime please come back to rust. i just found out that in very very hight traffic, rust beat the shit out of go, handling 2x bigger request per second than go lmao. also one of big company in my country just threw java out and switching to rust :
always has
First language which introduced exceptions was Lisp. IT didn't have "Try" either. Try block was introduced in CLU. So to say go doesn't have exceptions seems like an attempt to rewrite history. I mean you can say go doesn't have compiler either. It's go geter the conqueror of worlds. It just happens to do exactly the same thing compilers do..
bruh