Re: [rust-dev] Adding exception handling as syntax sugar with declared exceptions
On Sat, May 18, 2013 at 2:24 PM, Gábor Lehel illiss...@gmail.com wrote: On Thu, May 16, 2013 at 7:58 PM, Graydon Hoare gray...@mozilla.comwrote: I'm sympathetic to the desire here, as with all attempts to get exceptions right. Sadly I've never really seen it; I don't think anyone has really worked out the right way to work with catchable-exceptions in a language. -Graydon What's problematic about exceptions in C++, and what forces you to worry about exceptions everywhere, is that code inside a try-block and code outside of it may share state, which the code in the try block may be in various stages of modifying (along with allocating resources) when the exception is thrown, potentially leaving an inconsistent state and/or leaked resources at the point where the exception is caught. Therefore all functions have to be very careful that any mutable state accessible to the function is in (or reverts to) a consistent state at any point where an exception might be thrown, and that resources aren't left dangling, because who knows whether that mutable state might not be on the outside of a try-block and the function on the inside of it. What if instead of that, the language enforced that code in try blocks could not share any state with code outside, only allowing for data to be transferred in at the beginning and out (whether a result or an exception) at the end, and ensured that all resources acquired inside the try block were destroyed after it exited, no matter how it did? That would free the authors of individual functions from having to care about any of it, because if an exception is passing through their code, that means that from their perspective the world is ending, everything they have access to will be destroyed, so they can do whatever they want and it won't matter a bit. If that sounds familiar at all, it's because I just described the semantics of Rust's existing try()*. I still suspect that the best of all worlds would be something with the semantics of try(), or close, and the implementation and performance of traditional EH. Is that unrealistic? Am I way off base with this? An embarrassing misconception? To summarize my train of thought * Catchable exceptions can be implemented * But we don't want to, because it would force everyone to think about exception safety * That could however be avoided with appropriate restrictions * Rust's type system already gives us the tools to impose those restrictions, as evidenced by them being imposed on `try()` * Therefore it should be possible to have much of the benefit of catchable exceptions, without their drawbacks -- Your ship was destroyed in a monadic eruption. ___ Rust-dev mailing list Rust-dev@mozilla.org https://mail.mozilla.org/listinfo/rust-dev
Re: [rust-dev] Adding exception handling as syntax sugar with declared exceptions
On Thu, Jun 6, 2013 at 11:59 AM, Gábor Lehel illiss...@gmail.com wrote: Am I way off base with this? An embarrassing misconception? To summarize my train of thought * Catchable exceptions can be implemented * But we don't want to, because it would force everyone to think about exception safety * That could however be avoided with appropriate restrictions * Rust's type system already gives us the tools to impose those restrictions, as evidenced by them being imposed on `try()` * Therefore it should be possible to have much of the benefit of catchable exceptions, without their drawbacks The cost of spawning a task with try over a theoretical optimized exception handling scheme will only be the small stack segment it needs to allocate (likely cached). The scheduler will switch to it immediately, and if it doesn't do I/O there won't be a switch back until it's done. I think someone will need to demonstrate a performance issue with the completed scheduler before providing a special case in the language will seem like a serious suggestion. ___ Rust-dev mailing list Rust-dev@mozilla.org https://mail.mozilla.org/listinfo/rust-dev
Re: [rust-dev] Adding exception handling as syntax sugar with declared exceptions
On 06/06/2013 8:59 AM, Gábor Lehel wrote: Am I way off base with this? An embarrassing misconception? To summarize my train of thought * Catchable exceptions can be implemented * But we don't want to, because it would force everyone to think about exception safety * That could however be avoided with appropriate restrictions * Rust's type system already gives us the tools to impose those restrictions, as evidenced by them being imposed on `try()` * Therefore it should be possible to have much of the benefit of catchable exceptions, without their drawbacks No. The train of thought is that they _already are_ implemented to this level -- via task isolation[1] -- and people asking for catchable exceptions are (so far) actually asking for us to lift those restrictions[2], which we don't want to do. -Graydon [1] If it helps avoid wincing about the implied cost of spawning a task (allocating a segment, switching to it, and switching back on return) it might help to know that there are some serious discussions going on in the background about cactus stacks and the requisite scheduler hooks required to support cilk-like fork/join parallelism. [2] And/or asking to add first class language support for the idioms in the form of new keywords, RTTI or additional control structures. In case this is not obvious: we are trying to move as much as possible these days _out_ of the core language and into libraries and macros. This usually results in faster iteration, less fragility, fewer personnel and development/integration bottlenecks, and overall better implementations. ___ Rust-dev mailing list Rust-dev@mozilla.org https://mail.mozilla.org/listinfo/rust-dev
Re: [rust-dev] Adding exception handling as syntax sugar with declared exceptions
Date: Thu, 16 May 2013 10:58:28 -0700 From: gray...@mozilla.com To: bill_my...@outlook.com CC: rust-dev@mozilla.org Subject: Re: [rust-dev] Adding exception handling as syntax sugar with declared exceptions On 12/05/2013 8:00 PM, Bill Myers wrote: This is a suggestion for adding an exception system to Rust that satisfies these requirements: 1. Unwinding does NOT pass through code that is not either in a function that declares throwing exceptions or in a try block (instead, that triggers task failure) 2. Callers can ignore the fact that a function throws an exception, resulting in task failure 3. Callers can handle exceptions if desired, with the same syntax of C++, Java and C# (1) avoids the issue with C++ exception handling forcing to think about exception safety everywhere (2) avoids the issue with Java's checked exceptions forcing to explicitly handle or redeclare them (3) provides an easy-to-use exception mechanism Hi, sorry, I did mean to get back to this. I think this is ... unlikely to work out, or be something we incorporate into our repo. For a few reasons: - It's very late in the design cycle to be making changes of this scope. We're trying to stabilize. - It'll be a big performance hit on code paths that want to use such exceptions. Things that look like just function calls turn into quite extensive operations. We're already struggling with the costs of a return-bool solution for unwinding on platforms that have difficult normal EH (#4342). - Most seriously: it doesn't actually resolve (1) or (2), imo. (1) You'd still have to declare checked exceptions everywhere. But worse than in java, if you failed to declare them _anywhere_ you wouldn't get a compilation error, but a runtime failure. This is like making C++ default to 'throw ()', which tends to surprise everyone. Well, the idea is that exception would generally either be caught in the immediate caller or let go to task failure, with rare cases of propagation. The target use case are things like str_to_int functions that would throw format exceptions that would be caught by the immediate caller in case they want to handle it. (2) You still have to write in worry about exceptions everywhere style inside a try-block or function-that-rethrows. Only get to avoid it when you're insulating yourself by the implicit throw () declaration. Yes, but you can't always avoid this anyway, since in some cases operations are truly irreversible. For instance, if you want to make three HTTP POST requests and the second fails, then the first will have gone through and cannot be reverted automatically in any way, so the logic to handle we did something and then failed, leaving us in a half-done way is fundamentally necessary in some case regardless of language design. A limited version of this might be implementable by macros that could automatically generate a try_ version of a function with Result return type along with a version that fails. The only other approach that can improve on this is to have support for reversible execution. Basically, one would compile everything in two versions, where one of the versions keeps an undo log of memory writes. When a try block is entered, the compiler switches to the undo-log-enabled code, and when an exception is thrown, the log is played to undo everything. Hardware transactional memory like Haswell TSX can also be tried first if available. Of course, this wouldn't be able to handle external function calls and modifications of shared memory via unsafe pointers: this could be handled by having a special unsafe construct that passes a closure to unwind the changes. Calling irreversible functions like I/O would need to cause a transaction abort/throw an exception. Also, some things like RWARC modifications would be impossible to handle without a full STM implementation (with MVCC and so on). The biggest drawback of this is that it will double code size and compilation time. I'm not sure if it's worthwhile since it cannot work in general due to I/O, and simple cases can be handled with the syntax sugar proposed or doing the same by hand with Result return types and so on. ___ Rust-dev mailing list Rust-dev@mozilla.org https://mail.mozilla.org/listinfo/rust-dev
Re: [rust-dev] Adding exception handling as syntax sugar with declared exceptions
On Thu, May 16, 2013 at 7:58 PM, Graydon Hoare gray...@mozilla.com wrote: I'm sympathetic to the desire here, as with all attempts to get exceptions right. Sadly I've never really seen it; I don't think anyone has really worked out the right way to work with catchable-exceptions in a language. -Graydon What's problematic about exceptions in C++, and what forces you to worry about exceptions everywhere, is that code inside a try-block and code outside of it may share state, which the code in the try block may be in various stages of modifying (along with allocating resources) when the exception is thrown, potentially leaving an inconsistent state and/or leaked resources at the point where the exception is caught. Therefore all functions have to be very careful that any mutable state accessible to the function is in (or reverts to) a consistent state at any point where an exception might be thrown, and that resources aren't left dangling, because who knows whether that mutable state might not be on the outside of a try-block and the function on the inside of it. What if instead of that, the language enforced that code in try blocks could not share any state with code outside, only allowing for data to be transferred in at the beginning and out (whether a result or an exception) at the end, and ensured that all resources acquired inside the try block were destroyed after it exited, no matter how it did? That would free the authors of individual functions from having to care about any of it, because if an exception is passing through their code, that means that from their perspective the world is ending, everything they have access to will be destroyed, so they can do whatever they want and it won't matter a bit. If that sounds familiar at all, it's because I just described the semantics of Rust's existing try()*. I still suspect that the best of all worlds would be something with the semantics of try(), or close, and the implementation and performance of traditional EH. Is that unrealistic? * Along with the ability to actually observe the thrown exception, but IINM that's planned. And I guess you could have multiple variations on how to handle catching-and-or-rethrowing. If the plan is to use dynamic typing for the exception object you could have something like: implT ResultT, ~Typeable { fn catchE: Typeable(self, hndlr: fn(ex: ~E) - T) - T { match self { Ok(res) = res, Err(ex) = { match ex.cast::E() { Ok(casted_ex) = hndlr(casted_ex), Err(uncasted_ex) = fail!(uncasted_ex) } } } } } That doesn't really do catch block chaining well and I probably made 10 type errors, but oh well. -- Your ship was destroyed in a monadic eruption. ___ Rust-dev mailing list Rust-dev@mozilla.org https://mail.mozilla.org/listinfo/rust-dev
Re: [rust-dev] Adding exception handling as syntax sugar with declared exceptions
On 12/05/2013 8:00 PM, Bill Myers wrote: This is a suggestion for adding an exception system to Rust that satisfies these requirements: 1. Unwinding does NOT pass through code that is not either in a function that declares throwing exceptions or in a try block (instead, that triggers task failure) 2. Callers can ignore the fact that a function throws an exception, resulting in task failure 3. Callers can handle exceptions if desired, with the same syntax of C++, Java and C# (1) avoids the issue with C++ exception handling forcing to think about exception safety everywhere (2) avoids the issue with Java's checked exceptions forcing to explicitly handle or redeclare them (3) provides an easy-to-use exception mechanism Hi, sorry, I did mean to get back to this. I think this is ... unlikely to work out, or be something we incorporate into our repo. For a few reasons: - It's very late in the design cycle to be making changes of this scope. We're trying to stabilize. - It'll be a big performance hit on code paths that want to use such exceptions. Things that look like just function calls turn into quite extensive operations. We're already struggling with the costs of a return-bool solution for unwinding on platforms that have difficult normal EH (#4342). - Most seriously: it doesn't actually resolve (1) or (2), imo. (1) You'd still have to declare checked exceptions everywhere. But worse than in java, if you failed to declare them _anywhere_ you wouldn't get a compilation error, but a runtime failure. This is like making C++ default to 'throw ()', which tends to surprise everyone. (2) You still have to write in worry about exceptions everywhere style inside a try-block or function-that-rethrows. Only get to avoid it when you're insulating yourself by the implicit throw () declaration. I'm sympathetic to the desire here, as with all attempts to get exceptions right. Sadly I've never really seen it; I don't think anyone has really worked out the right way to work with catchable-exceptions in a language. -Graydon ___ Rust-dev mailing list Rust-dev@mozilla.org https://mail.mozilla.org/listinfo/rust-dev
[rust-dev] Adding exception handling as syntax sugar with declared exceptions
This is a suggestion for adding an exception system to Rust that satisfies these requirements: 1. Unwinding does NOT pass through code that is not either in a function that declares throwing exceptions or in a try block (instead, that triggers task failure) 2. Callers can ignore the fact that a function throws an exception, resulting in task failure 3. Callers can handle exceptions if desired, with the same syntax of C++, Java and C# (1) avoids the issue with C++ exception handling forcing to think about exception safety everywhere (2) avoids the issue with Java's checked exceptions forcing to explicitly handle or redeclare them (3) provides an easy-to-use exception mechanism The specification is based on adding some syntax sugar, which is then transformed by the compiler into the current Rust syntax, and thus does not really alter runtime behavior (but it is also possible to implement this with traditional unwinding if desired). Functions can be declared to throw exceptions, and if the conceptual unwinding process would have to pass through a function call that does not declare throwing an exception of that type, task failure is invoked instead. To integrate with the condition system, one can raise a condition instead of throwing, and declare the condition type and handlers with throws, allowing them to throw exceptions if desired (note that of course this will just result in task failure unless the function raising the condition is declared to throw). It is of course possible to code using this style by hand, but the syntax sugar allows to do it in a much terser way, and allows to convert functions that use task failure to functions that throw exceptions without changing the source code of callers that don't want to handle them. * New syntax added: - throws E1, E2, ... modifier on functions and closures - try/catch block - try() expression - throw e statement * New type declarations added: enum Result1T, E1 { Ok(T), Fail(E1) } enum Result2T, E1, E2 { Ok(T), Fail1(E1), Fail2(E2) } ... and so on... * Transformations to implement the system: Everywhere: fn foo() - T throws E1 [similar with closures] = fn foo() - Result1T, E1 fn foo() - T throws E1, E2 [similar with closures] = fn foo() - Result2T, E1, E2 try(f(args)) [if f declared with throws] = f(args) f(args) [if f returns T and is declared with throws, not inside try()] = match f(args) {Ok(x) = x, Fail1(e) = throw e, Fail2(e) = throw e, ...} In functions declared throws: return v = return Ok(v) try {try_body} catch(e1: E1) {catch_body} catch(e2: E2) {catch_body} = let mut e1_: OptionE1 = None; let mut e2_: OptionE2 = None; 'try_: loop {try_body; break;} if e1_.is_some() { let e1 = e1_.unwrap(); catch1_body } else if e2_.is_some() { let e2 = e2_.unwrap(); catch2_body } throw e, if there is any containing try block that has a catch block with argument eK with type EK such that e is convertible to EK (taking the innermost suitable try block and the first suitable catch block) = eK_ = Some(e); break 'try_ throw e, if there are no suitable try blocks but the function is declared throws and e is convertible to throws type EK = return FailK(e) throw e, otherwise, if e implements ToStr: = fail!(e.to_str()) throw e, otherwise: = fail!() ___ Rust-dev mailing list Rust-dev@mozilla.org https://mail.mozilla.org/listinfo/rust-dev