digitalmars.D - scope + destructor with Exception parameter for RAII
- Leandro Lucarella (65/65) Nov 28 2006 What do you think of adding an optional parameter (exception) to the
- Sean Kelly (11/16) Nov 28 2006 I don't like it, personally. It doesn't seem a good idea for a dtor to
- BCS (12/32) Nov 28 2006 I think this works under DMD
- Pragma (6/43) Nov 28 2006 Agreed. Although I don't know if this interferes with Sean's
- Sean Kelly (16/58) Nov 28 2006 The above example isn't the same thing. The in-flight exception is
- Sean Kelly (18/74) Nov 28 2006 By the way, the change I made will only trap exceptions thrown from an
- Sean Kelly (30/30) Nov 28 2006 One more follow-up and I promise I'll stop :-) I just remembered why I
- Leandro Lucarella (16/27) Nov 29 2006 Exceptions would not be thrown from the dtor, it just executes some code...
- Sean Kelly (20/46) Nov 29 2006 So are you saying you want this feature to allow Transaction to decide
- Leandro Lucarella (11/35) Nov 30 2006 I see this more as a hack than a clean solution (I don't say the dtor
- Sean Kelly (11/27) Nov 30 2006 Not at all, I use it all the time :-) But I don't think the C++ method
What do you think of adding an optional parameter (exception) to the destructor, defaulting to null, to indicate the destructor was called then unwinding because an exception was thrown? Then you can almost forget about scope(exit/success/failure) and you have a RAII as complete as Python's 'with' statement. So you can do something like: class Transaction { Database db; public: this(Database d) { db = d; db.begin(); } ~this() // success { db.commit(); } ~this(Exception e) { db.rollback(); } // ... } scope Transaction t = new Transaction(db); This concept could be extended to have as many destructors as you want, to handle different kind of exceptions, like: class Transaction { // ... ~this() // success { db.commit(); } ~this(DatabaseException e) { db.rollback(); // some recovery code } ~this(AnotherException e) { db.rollback(); // some other stuff } ~this(Exception e) { db.rollback(); } } I find it a little ugly and error prone =) If, for example, you have to use scope(exit/success/failure) you have to add yourself the finalization code on every use: Each time you want to use a transaction (for example) you have to write: Transaction t = new Transaction(db); scope(failure) db.rollback(); scope(exit) db.commit(); // ... code If you forget one scope(...) db.xxx(); you hit a bug. On the other hand, if you could just use: scope Transaction t = new Transaction(db); The code is simpler, less error prone, and plus you have all the Transaction logic in the transaction object, not in the code that uses the transaction. Opinions? Factibility? -- Leandro Lucarella Integratech S.A. 4571-5252
Nov 28 2006
Leandro Lucarella wrote:What do you think of adding an optional parameter (exception) to the destructor, defaulting to null, to indicate the destructor was called then unwinding because an exception was thrown? Then you can almost forget about scope(exit/success/failure) and you have a RAII as complete as Python's 'with' statement.I don't like it, personally. It doesn't seem a good idea for a dtor to alter its behavior based on whether an exception is in flight, and exceptions should never be thrown from dtors anyway. Doing so makes writing correct code far too complicated. One thing I have done for Ares, however, is to terminate the program if one exception is thrown while another is in flight. I think DMD/Phobos does not do this currently, and instead either ignores the new exception, or substitutes it for the in-flight exception (I can't remember which). Sean
Nov 28 2006
Sean Kelly wrote:Leandro Lucarella wrote:I think this works under DMD try { throw new Error("some stuff"); } catch(Error e) { throw new Error("more stuff\n"~e.toString); } It could be vary handy to have this ability to swap out one exception for another.What do you think of adding an optional parameter (exception) to the destructor, defaulting to null, to indicate the destructor was called then unwinding because an exception was thrown? Then you can almost forget about scope(exit/success/failure) and you have a RAII as complete as Python's 'with' statement.I don't like it, personally. It doesn't seem a good idea for a dtor to alter its behavior based on whether an exception is in flight, and exceptions should never be thrown from dtors anyway. Doing so makes writing correct code far too complicated. One thing I have done for Ares, however, is to terminate the program if one exception is thrown while another is in flight. I think DMD/Phobos does not do this currently, and instead either ignores the new exception, or substitutes it for the in-flight exception (I can't remember which). Sean
Nov 28 2006
BCS wrote:Sean Kelly wrote:Agreed. Although I don't know if this interferes with Sean's interpretation of the spec or not. I've done this in a few places myself, and I find it quite useful. -- - EricAnderton at yahooLeandro Lucarella wrote:I think this works under DMD try { throw new Error("some stuff"); } catch(Error e) { throw new Error("more stuff\n"~e.toString); } It could be vary handy to have this ability to swap out one exception for another.What do you think of adding an optional parameter (exception) to the destructor, defaulting to null, to indicate the destructor was called then unwinding because an exception was thrown? Then you can almost forget about scope(exit/success/failure) and you have a RAII as complete as Python's 'with' statement.I don't like it, personally. It doesn't seem a good idea for a dtor to alter its behavior based on whether an exception is in flight, and exceptions should never be thrown from dtors anyway. Doing so makes writing correct code far too complicated. One thing I have done for Ares, however, is to terminate the program if one exception is thrown while another is in flight. I think DMD/Phobos does not do this currently, and instead either ignores the new exception, or substitutes it for the in-flight exception (I can't remember which). Sean
Nov 28 2006
Pragma wrote:BCS wrote:The above example isn't the same thing. The in-flight exception is caught and a different one is rethrown. What I consider illegal is something like this: try { throw new Exception( "A" ); } finally { throw new Exception( "B" ); } What exception is in-flight after the finally block completes? And what happens to the other exception? The only correct behavior here is to terminate the application. SeanSean Kelly wrote:Agreed. Although I don't know if this interferes with Sean's interpretation of the spec or not. I've done this in a few places myself, and I find it quite useful.Leandro Lucarella wrote:I think this works under DMD try { throw new Error("some stuff"); } catch(Error e) { throw new Error("more stuff\n"~e.toString); } It could be vary handy to have this ability to swap out one exception for another.What do you think of adding an optional parameter (exception) to the destructor, defaulting to null, to indicate the destructor was called then unwinding because an exception was thrown? Then you can almost forget about scope(exit/success/failure) and you have a RAII as complete as Python's 'with' statement.I don't like it, personally. It doesn't seem a good idea for a dtor to alter its behavior based on whether an exception is in flight, and exceptions should never be thrown from dtors anyway. Doing so makes writing correct code far too complicated. One thing I have done for Ares, however, is to terminate the program if one exception is thrown while another is in flight. I think DMD/Phobos does not do this currently, and instead either ignores the new exception, or substitutes it for the in-flight exception (I can't remember which). Sean
Nov 28 2006
Sean Kelly wrote:Pragma wrote:By the way, the change I made will only trap exceptions thrown from an object's dtor, as it was a change to the finalizer code. The above will work just fine, with only one of the two exceptions escaping. Same as: scope(exit) throw new Exception( "B" ); throw new Exception( "A" ); I could probably be convinced that the throw from the finally block should simply be ignored, as the finally block is intended to do some simple clean-up, but the scope(exit) bit above is clearly a programming error. Throwing from an object's dtor is a lot closer to throwing from scope(exit) than finally IMO. Because while they are functionally identical, I think they are likely to be used in different ways. With a finally block, the programmer is stating that he expects an exception to be thrown and that the code should execute regardless. I'm still not certain that I like the idea of ignoring an exception from a finally block, but it bothers me less than ignoring exceptions from dtors or scope(exit)/scope(failure) blocks. SeanBCS wrote:The above example isn't the same thing. The in-flight exception is caught and a different one is rethrown. What I consider illegal is something like this: try { throw new Exception( "A" ); } finally { throw new Exception( "B" ); }Sean Kelly wrote:Agreed. Although I don't know if this interferes with Sean's interpretation of the spec or not. I've done this in a few places myself, and I find it quite useful.Leandro Lucarella wrote:I think this works under DMD try { throw new Error("some stuff"); } catch(Error e) { throw new Error("more stuff\n"~e.toString); } It could be vary handy to have this ability to swap out one exception for another.What do you think of adding an optional parameter (exception) to the destructor, defaulting to null, to indicate the destructor was called then unwinding because an exception was thrown? Then you can almost forget about scope(exit/success/failure) and you have a RAII as complete as Python's 'with' statement.I don't like it, personally. It doesn't seem a good idea for a dtor to alter its behavior based on whether an exception is in flight, and exceptions should never be thrown from dtors anyway. Doing so makes writing correct code far too complicated. One thing I have done for Ares, however, is to terminate the program if one exception is thrown while another is in flight. I think DMD/Phobos does not do this currently, and instead either ignores the new exception, or substitutes it for the in-flight exception (I can't remember which). Sean
Nov 28 2006
One more follow-up and I promise I'll stop :-) I just remembered why I changed Ares in the first place. Since objects are finalized during GC collections, throwing from a dtor results in completely unpredictable program behavior. For example: { class MyClass { ~this() { throw new Exception( "die" ); } } MyClass c = new MyClass(); } OtherClass c = new OtherClass(); // A In the program above, an exception may be thrown from point A that has *nothing to do with an out of memory condition* and worse, it will be thrown only if program memory is in a state where the GC needs to collect to free up resources. It's even possible that the instance of MyClass could have been declared and allocated in a completely different thread, resulting it its being passed up a call stack that was not written to expect such a condition. So the presence of a GC and GC-called finalizers makes throwing from dtors even worse than it is in deterministic situations (which is still quite bad). Also, I have found few instances where an exception really needs to be thrown from a dtor. While resource cleanup operations may indeed fail, more often than not the documentation will say that a failure may only occur if the parameters are invalid. And with proper encapsulation is is guaranteed not to happen. Sean
Nov 28 2006
Sean Kelly escribió:Leandro Lucarella wrote:Exceptions would not be thrown from the dtor, it just executes some code if it was executed during an unwinding because an exception was raised: scope Transaction t = new Transaction(); throw Exception("ouch!"); Here the Transaction.~this(Exception) is called, but there is no Exception throwing in the dtor. I don't see why writing correct code is that complicated. And how do you address the problem of repeating error handling code and the lack of encapsulation of scope(success/failure)? Any other thoughts on this (the thread diverged a little from the original topic ;)? -- Leandro Lucarella Integratech S.A. 4571-5252What do you think of adding an optional parameter (exception) to the destructor, defaulting to null, to indicate the destructor was called then unwinding because an exception was thrown? Then you can almost forget about scope(exit/success/failure) and you have a RAII as complete as Python's 'with' statement.I don't like it, personally. It doesn't seem a good idea for a dtor to alter its behavior based on whether an exception is in flight, and exceptions should never be thrown from dtors anyway. Doing so makes writing correct code far too complicated.
Nov 29 2006
Leandro Lucarella wrote:Sean Kelly escribió:So are you saying you want this feature to allow Transaction to decide whether to roll-back or commit? I had assumed it was to determine whether it was "safe" for Transaction's dtor to throw an exception itself.Leandro Lucarella wrote:Exceptions would not be thrown from the dtor, it just executes some code if it was executed during an unwinding because an exception was raised: scope Transaction t = new Transaction(); throw Exception("ouch!"); Here the Transaction.~this(Exception) is called, but there is no Exception throwing in the dtor.What do you think of adding an optional parameter (exception) to the destructor, defaulting to null, to indicate the destructor was called then unwinding because an exception was thrown? Then you can almost forget about scope(exit/success/failure) and you have a RAII as complete as Python's 'with' statement.I don't like it, personally. It doesn't seem a good idea for a dtor to alter its behavior based on whether an exception is in flight, and exceptions should never be thrown from dtors anyway. Doing so makes writing correct code far too complicated.I don't see why writing correct code is that complicated. And how do you address the problem of repeating error handling code and the lack of encapsulation of scope(success/failure)?The lack of encapsulation doesn't bother me much, though now I see what you're getting at. I do think that having: auto scope t = new Transaction(); scope(failure) t.rollback(); // commits if not rolled back on scope exit, alternately use scope(success) t.commit(); actually aids readability a bit, at the expense of some extra code. That said, I have considered adding a routine that the user can call to determine whether an exception is in flight. Similar to the one in C++, but without all the annoying shortcomings. It would mean setting a thread-local flag or pointer in the internal exception handling code, etc. I think this is a better approach than altering dtor syntax for the same purpose, as it avoids language changes and doesn't lose any usefulness in the process.Any other thoughts on this (the thread diverged a little from the original topic ;)?See above :-) Sean
Nov 29 2006
Sean Kelly escribió:So you are against all RAII done in the C++ way, I guess...I don't see why writing correct code is that complicated. And how do you address the problem of repeating error handling code and the lack of encapsulation of scope(success/failure)?The lack of encapsulation doesn't bother me much, though now I see what you're getting at. I do think that having: auto scope t = new Transaction(); scope(failure) t.rollback(); // commits if not rolled back on scope exit, alternately use scope(success) t.commit(); actually aids readability a bit, at the expense of some extra code.That said, I have considered adding a routine that the user can call to determine whether an exception is in flight. Similar to the one in C++, but without all the annoying shortcomings. It would mean setting a thread-local flag or pointer in the internal exception handling code, etc. I think this is a better approach than altering dtor syntax for the same purpose, as it avoids language changes and doesn't lose any usefulness in the process.I see this more as a hack than a clean solution (I don't say the dtor Exception parameter is heaven but I see it a little more cleaner =), but its fair enough. I didn't know C++ had a way to determine an exception is in flight...Thanks, really =) -- Leandro Lucarella Integratech S.A. 4571-5252Any other thoughts on this (the thread diverged a little from the original topic ;)?See above :-)
Nov 30 2006
Leandro Lucarella wrote:Sean Kelly escribió:Not at all, I use it all the time :-) But I don't think the C++ method works well for situations like the above. Andrei's original series of articles on scope guards actually used transactions as their primary example for when the C++ method falls apart, and I agree with his reasoning. That said, part of his argument *was* because the C++ method for detecting in-flight exceptions isn't very reliable. Providing a fixed version in D would allow for both methods to be used--it's just a matter of making the necessary changes. All of which could be done in phobos/internal, by the way. SeanSo you are against all RAII done in the C++ way, I guess...I don't see why writing correct code is that complicated. And how do you address the problem of repeating error handling code and the lack of encapsulation of scope(success/failure)?The lack of encapsulation doesn't bother me much, though now I see what you're getting at. I do think that having: auto scope t = new Transaction(); scope(failure) t.rollback(); // commits if not rolled back on scope exit, alternately use scope(success) t.commit(); actually aids readability a bit, at the expense of some extra code.
Nov 30 2006