4 ms·
A language's error-handling story is a central part of the experience of using that language. Especially if that language is a systems language. Rust's error h
by softc 10y ago
A language's error-handling story is a central part of the experience of using that language. Especially if that language is a systems language.
Rust's error handling still seems unnecessarily verbose to me. A specific error type is only necessary if the program can handle the error in question. Rust has acknowledged this but I still don't want every function call in my code to be prefixed with "try!" or suffixed with "?!"
Rust error handling is also inefficient: in C++, if no error happens, then no code is run, whereas in Rust, even if no error happens a return code must be checked.
I will sadly continue to happily use C++ until Rust improves its error-handling story.
- oconnor663 10y ago> Rust error handling is also inefficient: in C++, if no error happens, then no code is run, whereas in Rust, even if no error happens a return code must be checked. In this sense Rust's error handling is just like C (though harder to forget to check). Would you say C++ is more efficient than C in error handling?
- softc 10y agoYes I would. In modern C++ we use exceptions and RAII for automatic cleanup.
- mmstick 10y agoExceptions have high cost so I don't see where you are coming from. Rust's RAII goes further along than C++'s. C++ is playing a game of catchup to steal the move semantics from Rust.
- olejorgenb 10y agoAccording to http://stackoverflow.com/a/13836329/1517969 http://stackoverflow.com/a/13836329/1517969 modern c++ exceptions are free when no throw occurs.
- mmstick 10y agoIn addition, Rust Results/Options are free when no throw occurs. So the argument is moot.
- softc 10y agoNo they aren't, you must issue a compare instruction to verify the result was successful.
- oconnor663 10y agoDo you know whether branch prediction makes them effectively free, in places where the result is almost always successful?
- softc 10y agoException handling in C++ is absolutely zero-cost when no exception occurs. Look up the Itanium ABI. In Rust, even if no error occurs, the return value must still be checked. Additionally, Move semantics were borrowed from C++, not the other way around.
- sidlls 10y agoI have almost the exact inverse view: when I write C++ code, I shed a tear if I can't disable exceptions and use something very analogous to Rust's Result type. I can't tell you how many times I've written some version of 'class Result<T> { union {int, T} }' (obviously, that's illustrative, given all the extra work that has to go into writing a proper tagged-union in C++) for use with C++ code at jobs I've had in the past. Rust's implementation here is great, in my opinion.
- lacampbell 10y agoAs an aside: I'm curious as to why you'd implement a Result type in C++ as a tagged union where you have to manually branch on the tag, and not as an abstract class with two children.
- sidlls 10y agoI find it easier to reason about.
- lacampbell 10y agoInteresting. I tend to view ADT as syntax sugar for a shallow class hierarchy of small immutable objects with no encapsulation or behaviour.
- dbaupp 10y agoThe union avoids requiring an allocation for, say, returning a Result.
- lacampbell 10y agoI am not sure what using an object vs using a struct with an enum + a union has to do with allocation. My C++ is rusty (no pun intended).
- deleted 10y ago[deleted]
- akiselev 10y ago> Rust error handling is also inefficient: in C++, if no error happens, then no code is run, whereas in Rust, even if no error happens a return code must be checked. Just because you don't have to write the code doesn't mean that it doesn't exist! Exceptions are not a zero cost abstraction. Every C++ functional call that can throw adds several instructions of overhead to save information about the call stack and check the return value so even when you don't have an exception you still have the same amount of overhead as all Rust errors. Options are simple types that can often be optimized to the size of T in Some(T) and they're on the stack so worst case is you need an extra usize slot in your stack. In the case of success, Rust is at least as efficient with errors as C++ but far more efficient when errors do occur.
- cesarb 10y ago> Every C++ functional call that can throw adds several instructions of overhead to save information about the call stack and check the return value That's not how it works (unless you're using 32-bit Windows). I wrote a long explanation using an old gcc on Linux at https://stackoverflow.com/questions/307610/how-do-exceptions-work-behind-the-scenes-in-c/307716#307716 https://stackoverflow.com/questions/307610/how-do-exceptions... Basically, calling a function that can throw doesn't save any information (other than what it already has to save to do the call), and it doesn't check any return value (other than any check on the return value you wrote yourself). Instead, the exception machinery uses compiler-generated tables, keyed to the return address, to direct the execution flow to the correct landing pad.
- softc 10y agoIn C++ with the Itanium ABI, exceptions only add overhead when thrown, not on every function call. In Rust, the result of every function call is checked.
- akiselev 10y agoI see, my knowledge of exceptions is quite out of date then. Regardless, both of those work out to the same performance because Rust's single compare will be optimized away by the branch predictor except when errors occur. In the error case, C++ exceptions would have a lot more overhead that you would have to explicitly opt into in Rust on a case by case basis (so no need to disable errors altogether).
- mmstick 10y agoThe `?` operator is just syntax sugar for: let value = match attempt_something() { Ok(value) => value, Err(error) => return SomeErr::Kind(error), } That way you only need to type let value = do_something().map_err(SomeErr::Kind)?; It was called for by the community because error checking is something that can happen multiple times within the same function. Simply passing the error up the chain and handling all your errors in a central location is highly convenient, especially when matching requires that you handle all cases. Additionally, in no way is C++ more efficient at error handling that Rust. Rust is able to perform this with zero allocation costs.
- akiselev 10y agoThe best part is that you can use Into/From conversion traits and use a different error type all through the call chain. A bunch of functions returning error type A and calling functions with error types B, C, D, and E can all just pass through the errors even though they return a different type. You implement the conversion trait once for each error and then it just works for all functions with no other boilerplate.
- softc 10y agoWith this method, do you have to implement the From trait for every pair of errors that can be propagated up the stack?
- softc 10y agoI understand exactly what "?" does, I just don't want to have to type it every time I call a function that can fail. Additionally I don't want to have to distinguish between which functions fail and which don't, I'd rather assume all functions fail and make sure my code is robust enough to handle failure at any point. This is my preference. C++ is more efficient at error handling than Rust. In the common case of no error happening, Rust must check a return code, but C++ doesn't have to because of how exceptions are implemented in the Itanium ABI "zero-cost exceptions".
- mmstick 10y ago