This video is superb - you’ve demonstrated the problem, and then the solution’s superiority. Almost a video which can serve as a good selling point for Rust!
@@lizzyfleckenstein9837 I meant the method names and code to setup the errors was verbose. The messages themselves are not important. I'm talking about ergonomics of the api.
While it is a nice API, it feels like a lot of effort for little reward. Consuming all this time using an API one has to learn, to eventually produce messages you could print directly. There has to be something made simple with little to remember and faster to write. We programmers understand stack errors. Why can't we just have a stack without fancy terms and methods/functions?
I would be interested in a comparison between error stack and miette. I kinda prefer the miette naming scheme and output. In general, "crate battle" would be a cool video format. Other ideas for this format: egui vs iced vs yew, bevy vs microquad,
this is an awesome video! I usually build huge enums but this approach looks awesome! perhaps it would be confusing to new developers reading the code and seeing this approach for the first time. but overall looks great
I'm struggling to understand the usefulness of the crate if we already have access to expect() and match error handling. Perhaps I'm missing something?
Perhaps it has to do with style of programming and problem-space. For the things I'm currently working on, it is useful to see the errors in context to each other. Expect is incredibly useful, but sometimes you want more information around it. I also would not introduce this crate in my project until it reached a point of complexity that leaves me wishing I had better ways to handle my errors, but this is just me. I can easily see the vast majority of projects doing just fine with only what std comes with by default.
So "expect()" just panics (calls the panic!() macro) with a custom error message. Very often, especially if you write library code that is consumed by others, or even when you write a simple webservice and you don't want to end up with a "500 internal server error" on the client/browser you will need to be able to customize your error handling and decide when and how to return a proper error, or - and this is the worst-case scenario - when you actually have to call panic!(). Calling panic!() is a "using a last resort"-kinda situation and should only be used very sparingly in my opinion. Ask yourself this: if you write a database server/service and a user sends an invalid query, do you panic!() and force a shutdown of the whole database or do you return a nicely formatted error message to the user telling them their query has an issue (and ideally what issue and where (and how to fix it, if possible))? Same thinking goes for any kind of web-service/web-app. Those examples I described above are usually multi-threaded, because they serve not just one single client at a time, but can serve multiple clients at the same time. Do you really want to have the whole service exit/go down just because one client is misbehaving? No, of course not. Never actually. Also, of course I am over-simplifying things here, even calls to "panic!()" in Rust can be "intercepted", kinda - it is a quite advanced feature to use for sure, so totally not beginner friendly and hence not widely advertised. Also it is certainly not as easy in Rust as it is in other programming languages who provide a more traditional exception-based error handling, that's for sure. So, in conclusion from my point of view: your argument is completely understandable from a beginners (or even intermediate) Rust programmer's point of view, so I am glad you asked. From what I understand so far (and I've only used Rust for the last 2~3 years), it is like I explained above (someone more experienced in Rust please correct me when I am wrong (not if, because I am most likely wrong with some detail or another). That being said: error handling in Rust is still one of, if not, _the_ language features I struggle with the most. The borrow checker is "easy mode" compared to this (for myself that is, _at this point in time_ 😊).
Ooooohhhhhhhhh. @ 12:00 Now I see what they're going for. Thanks. I'd been curious about this library, but didn't quite grok what they were offering from a few skims. I'd be curious to hear how you think this compares to Miette -- which was around before, gathering error information into a Report type and sending forth. But with, it seems, more focus on user usable errors.
This is one of the important videos. Error handling is super important IMO, I prefer the clean and deep context of error log. Moreover it provides documentation as well! Thanks 😊
What font and what color scheme do you use? It looks different from what we have on Windows and Linux, but maybe it's because you're running VSCode on macOS.
Font: JetBrains Mono Color theme: Dark+ (available by default) Parentheses are colored in yellow/blue/purple too, thanks to the Bracket Pair Colorizer... which I just learned today has become a VSCode built-in. 7_7
Cool but cannot shake off the feeling this is somewhat overengineered. Would like to see some more lightweight approach. Anyhow and thiserror looks quite fine to me even though it's not as robust as this approach. Last detailed root cause for the error was excellent, though!
Standard error reporting is better. By that I mean the traditional: "Error: Cause: Sub-cause: Sub-sub-cause: etc." Reporting the file + line is not good for user-final error reporting. Also, the report tree is also not suitable for user information, who would like something much more simple and direct. Also, if you need file + line information for every error, you're doing your error handling wrong. Rust "Result" error handling is supposed to be "final" and "complete". Where you need file + line info is in unexpected situations (panics), where you'll get that info. Just use traditional error handling coupled with 'thiserror' crate, that will help you achieve high level of completeness and accuracy. This also adds too much bloat to the code itself. Also, you need to worry about "am I actually handling errors, or am I just reporting them?". Using this or "anyhow", you might end up with a program that does not handle errors, just reports them. If only you developer use the program, it might be OK, but not for the general user.
Awesome, but what would be easiest way to remove all lol logging from release build..? I mean it's perfect for debugging but what if we want to release the code?
I would like to learn rust, but reading the code with a lot of &&, ?, ::,! - I know this is a specific of the language and this is a basics everyone should know. But if you look at the python code even though you haven't met it before, you will understand what it does. In here I see a lot of code for error handling (switching context, attaching printable, etc.) In most cases all of this code should be a template, can we achieve the same result with less amount of code?
I don't like the "error-stack" crate approach at all. It mixes bunches of successful path execution statements with bunches of failure path execution statements (e.g., parse() with report() or collect() with change_context()) as opposed to factoring out error creation and transformation into separate blocks (From trait usage). This intermingled approach is used in reactive programming libraries such as OpenCombine and it gets so tricky that it can confuse the livin' sh*t out of everyone except for absolute experts. Rust's golden rules are simplicity, readability and predictability - and that probably ain't it.
It's an incredible oversight (stupid decision) to not have exceptions in Rust and then having to develop stuff like this to emulate it in a _very_ cumbersome way.
Exceptions is a bad concept. Errors and mistakes are parts of life and expected to happen, there is nothing exceptional in them. You shoud treat errors just like another valid code branch in your program.
Not bad but all this inline error handling (actually, data checking in this case) makes the actual program logic rather unreadable. I still prefer the C-style "if check1_fails return; ...if check_n_fails return; ...do_the_work;" I know Rust enforces error checking, which is good, but it would be nice if all that checking could be moved up in the code.
Actually, every single "if check_fails { return }" is still there, condensed into a single "?" operator at the end of every function call. It's just that before we get to that point, we attach a lot more information to the error message. You can still do that style as shown at 13:18. However, there are some cases where you cannot simply return immediately as you error. Rust encourages generous use of iterators, which are functional in nature. This usually means there is work done within "closures"; anonymous functions. You have to handle errors within those closures, and only rely on the upper levels to add more context.
Thanks Bogdan for showcasing the "error-stack" error handling crate/library. Very cool stuff. And I applaud you for the effort you went through to show how this can really be used to make a dev's life easier when debugging programs out in the wild! Just set the logging level accordingly and you'd get enough info to pin-point an a problematic code path/etc. 👍👍👍! Honestly, there is about 20+ software development RU-vid channels I have subscribed to, but yours is in the TOP 3 to watch for sure (no questions asked!)!!! Especially if one is interested or working with Rust. You are doing a banger of a job here! Thank you! 👍👍👍
Using only std, you can use Result::map_err to change the error type. You should also make all format! macros lazy. Else, the string will be created every time the function is called.
I kind of like it, because it creates printable messages in one "logical" line, that may or may not be printed/logged/displayed later, but at the same time this creates a problem if we want to localise the application later, because we haven't separated data from presentation. Can this approach be combined with "thiserror" to derive the Display and Error traits for our types instead of providing them manually?
Thank you for showing your face as you speak. When the topic gets complicated it helps me understand what you're saying when I can see your face and mouth move as you speak. Also, thank you for not speaking while you put code into the IDE. It is helpful to be able to focus on what is being written.
Where is the problem in returning an Box everywhere multiple Errors are thrown in one function? Also this version is very verbose in my opinion. I will use the normal error trait instead.
Thanks for the video and the effort of improvement made along different videos. I see you are now working on a mac and another editor. Which one is it?
Excellent tutorial! I just disagree on the user-facing 'Try again' message. There is nothing to try again, the program will consistently fail. I would even argue this is the most frustrating type of error message you can give to anyone, unless trying again actually has merit. In this case, a message like 'Entry for {name} was found, but is malformed and cannot be returned.' would be much more explicit and suitable. This is a message you can then pass back to the dev, or google for and get some proper fix for it.
Don't cut the corners with the simple yet incomplete solutions. For the libraries I recommend `thiserror` lib, and for the applications - `anyhow`, both are maintained by David Tolnay and extended by community. Also they are tested in production, so for me the choice for the error handling library is just no-brainer. 😉