|
|
Subscribe / Log in / New account

Great!

Great!

Posted May 16, 2015 17:05 UTC (Sat) by ncm (guest, #165)
In reply to: Great! by epa
Parent article: Rust 1.0 released

In principle, try! and Result implement a library-level exception mechanism. To the degree that all code is expected to use them, it might have been better to build them into the core language, so you could declare when you are *not* using them. That leads to something like C++'s noexcept, which introduces the wrinkle that it's sometimes messy (and error-prone) to express whether something might throw. In C++, overly-pessimistic declarations result in copying when you expected to move. Overly optimistic declarations result in abort-on-throw, if something does throw.

C++ needed noexcept to support move semantics on objects in containers, where moving can throw. In rust, moving is a much simpler operation, and can't fail. The consequences of declaring a Result return unnecessarily should be less than of failing to propagate noexcept on a C++ move constructor.

Since I gather most of the standard traits don't (yet?) allow for returning Result, it remains to be seen how deeply the discipline of comprehensive failure handling can penetrate. The reason to hope for improvement is that the rust community have routinely embraced changes that break every existing program. People are understandably reluctant, though, to wrap every statement in a try! block. Rust may be forced to accept a flavor of exception mechanism, which would be less onerous if it doesn't come with a demand for mannered "exception-safe" style.


to post comments

Great!

Posted May 19, 2015 17:02 UTC (Tue) by ofranja (guest, #11084) [Link] (9 responses)

Using try! macros is one alternative, but I see this mechanism more like an attempt to present proper error handling into an "idiomatic exception handling" format without weakening Rust safety guarantees.

However, I fail to see how it could be called an exception handling mechanism, as it has very different properties from any exception handling mechanism and doesn't rely on separate unwinding stacks.

Personally, I would rather see Rust going towards a monadic or any similar (sounder) approach than falling in the tales of exception handling à lá C++ - doing otherwise would only weaken its safety and bring the language backwards in terms of safety.

Great!

Posted May 21, 2015 13:06 UTC (Thu) by ncm (guest, #165) [Link] (8 responses)

If every call to a function that might fail has to be wrapped in a try! block, and every such function return a Result, it is a poor man's exception mechanism implemented piecemeal by users of the language. "Poor man's" because it requires clutter at every call site and every function declaration, and subject to omissions when somebody doesn't participate correctly. "Exception mechanism" because what happens in a try!/Result is no different, in principle, from unwinding one stack frame. Pile them up, and you unwind as much of the stack as an exception thrown would, but with more source-code clutter.

How can you know, when you make a trait, whether the implementation might fail? The only sensible spec for any function you aren't implementing yourself is to return Result. Changing it later requires changing every caller.

This is exactly the sort of thing that is better automated in the language. Fear of the "exceptions" boogie man leads you straight into the arms of his much-worse cousin.

Since Rust lacks every one of the flaws that C++ inherits from C that make exception-safe programming difficult and hazardous, there is nothing to fear from exceptions. Make everything return a Result by default and you're halfway there. Make every function body implicitly a try! block and you're 90% there, painlessly. The rest is code you would be obliged to write anyway.

Great!

Posted May 21, 2015 15:25 UTC (Thu) by cesarb (subscriber, #6266) [Link] (2 responses)

> Make everything return a Result by default and you're halfway there.

What about std::ops::Drop (http://doc.rust-lang.org/stable/std/ops/trait.Drop.html)? What would the compiler do with the return value of its drop method?

Great!

Posted May 22, 2015 5:51 UTC (Fri) by ncm (guest, #165) [Link] (1 responses)

Destructors never throw in C++. (They could, in principle, but it would be stupid to code it.

Great!

Posted May 22, 2015 13:43 UTC (Fri) by jwakely (subscriber, #60262) [Link]

And since C++11 you have to advertise your stupidity by putting noexcept(false) on the destructor, otherwise if it tries to throw it will terminate the program. That's a pretty big incentive to avoid throwing from destructors.

Great!

Posted May 21, 2015 19:26 UTC (Thu) by ofranja (guest, #11084) [Link] (4 responses)

> If every call to a function that might fail has to be wrapped in a try! block, and every such function return a Result, it is a poor man's exception mechanism [...]

I'm talking about error handling, exceptions are an specialization of that. I understand the analogy, but keep in mind it doesn't have to be done by wrapping everything in a try!() - albeit that's more idiomatic for people used to exception handling mechanisms.

> "Exception mechanism" because what happens in a try!/Result is no different, in principle, from unwinding one stack frame. [...] How can you know, when you make a trait, whether the implementation might fail? The only sensible spec for any function you aren't implementing yourself is to return Result. Changing it later requires changing every caller.

Some of which can be considered advantages depending on your point of view. Changing all the callers might be interpreted as a feature: the compiler is able to state that your error handling is unsound, instead of the programmer having to do manual inspection of every single caller because the compiler won't be able to catch the inconsistencies.

Also, you miss the point of the trait declaration here: if you are implementing a trait you should indeed use Result<T,E>, unless you *want* the function not to return an error, in which case the implementor would have to fail!(). That's an *explicit* way to encode if errors should be handled or returned, in a compiler-checked way, which is far superior comparing to the guarantees of the exception mechanism in C++.

> This is exactly the sort of thing that is better automated in the language. Fear of the "exceptions" boogie man leads you straight into the arms of his much-worse cousin.

This is where we disagree. If you think using a C++ mindset, you'll think using the type system is a weaker alternative, but that's because C++ cannot express code the same way, at least not without a lot of boilerplate inplace. Rust can easily use the type system to assure your code is sound regarding error handling too, which is much more powerful and another great feature of the language.

> Since Rust lacks every one of the flaws that C++ inherits from C that make exception-safe programming difficult and hazardous, there is nothing to fear from exceptions. Make everything return a Result by default and you're halfway
> there. Make every function body implicitly a try! block and you're 90% there, painlessly. The rest is code you would be obliged to write anyway.

Maybe a static/checked exception mechanism would have some similar properties, but it would bring some additional issues too - and complexities.

I'd rather go to a monadic approach, still allowing the user to explicitly fail!() when it can't deal with the error in a sensible way. Falling back to exception approach à lá C++ would only create implicit holes in the soundness of the programs, which IMHO would be a major step back in the language.

Great!

Posted May 22, 2015 6:51 UTC (Fri) by ncm (guest, #165) [Link] (3 responses)

Apparently you are not aware that a C++ program can call abort() at any time? Or ignore exceptions and let terminate() be called automatically, which calls abort()? There's nothing innovative or clever about fail!. A C++ function can, as in Rust, return an object that must be cleaned up manually, but it is not done because that would be bad design. Talking about "mindset" seeks to distract attention from a failure of reasoned argument. You can pretend that weakness is really an advantage, but the argument refutes itself: C is weaker yet, but not therefore better.

Manifestly, any regime that makes you fill your program with boilerplate try! blocks (or worse) and Result<> apparatus is weak. Any that makes it impossible to recover gracefully from failures is worse. If your type system increases your cognitive load you are worse off, and no compounding of monads will save you. The type system can be twisted up in increasingly labored knots to try to encompass error handling, but the resulting mess just demonstrates it is a poor match for the job.

Rust implements lots of new, innovative, and good ideas, but it still can easily fail, and will if the response to its failings is to insist it is better for them. C++ started out with many flaws inherited from C, and succeeded because it has never suffered from delusions of perfection.

Great!

Posted May 22, 2015 8:18 UTC (Fri) by ofranja (guest, #11084) [Link] (2 responses)

I'm well aware of the explicit abort() and the std::terminate() implicit execution - either by ignoring exceptions or throwing inside a destructor. I had my years of pain with C++ already.

What I'm talking about is not how fail!() is clever or how "weakness" is good, but how modeling a language over an explicit model of error handling is better than an implicit one.

In that sense, it's indeed a C++ mindset to consider it inferior to C++ only because in C++ that would be bad design. I can say a Haskell programmer - for instance - would have very different opinions about exceptions, error handling, and good design. The same way, Rust and C++ are different languages, so not necessarily the same choices apply.

I'm not talking about perfection here - but soundness. It's tempting to throw soundness away when we are faced with the complexity of the problem, but doing so for the sake of some pre-concepted syntatic pattern or some quirky language construct makes little sense when you are trying to make a real improvement on the language level.

Great!

Posted May 23, 2015 16:04 UTC (Sat) by ncm (guest, #165) [Link] (1 responses)

Ideological purity is very appealing up to the point that it begins to actively interfere with sound system engineering principles. At that point, its main effect is to restrict use of the language to toy applications and academia. Toys are fun, but we have an overabundance of toy languages already. It would be tragic if a language with as much promise as Rust retreated to toyland.

An alternative would be to fork the language, and let Rustoy go the way of so many before it, while those of us who have serious engineering goals get on with them. That would be unfortunate but not tragic.

Great!

Posted May 23, 2015 16:50 UTC (Sat) by ofranja (guest, #11084) [Link]

Sound system engineering principles are a form of ideological purity.

I'd rather view this as a design decision instead of just ideological purity - afterall, if you are trying to design a safe systems language, you have to limit the unsafeness to strictly necessary points. If you are trying to argue that safeness and soundness are not important, Rust might not be a proper fit for your needs.

I guess Rust is not trying to be the glorious successor of C++ or a new C++ with aesthetic improvements - as others like D did - but it's trying to be something different. And this might be the key point of its success.


Copyright © 2025, Eklektix, Inc.
Comments and public postings are copyrighted by their creators.
Linux is a registered trademark of Linus Torvalds