From: ale@... Date: 2001-01-20T18:22:28+09:00 Subject: [ruby-talk:9593] Re: rescue / ensure On Sat, 20 Jan 2001, Bernd Lorbach wrote: > Hi, Hello, > I don't understand what the ensure-clause is for. I suggest you to do some testing where you try to exercise it a bit, wonder a little and then ask again. But in this case, your example below shows clearly you've done part of that already, so I'll show you an example where the difference matters (and hopefully clarifies it). > If you have > > f = File.open ... > begin > ... > rescue > puts "Error: #{$!}" > ensure > f.close > end > > you just could do > > f = File.open ... > begin > ... > rescue > puts "Error: #{$!}" > end > f.close > > I think the latter one looks more symmetric and does just the same > as the former version, as the rescue clause ensures, that the > program is continued after the begin/end block. > Am I missing something? The thing is, the two examples of above code are not equivalent, and in more general case even more care is needed to emulate ensure's behaviour with plain rescues. Try to run these examples: def go_away puts "exiting at go_away" exit end begin puts "raising" raise puts "going" go_away puts "went" rescue puts "rescued" ensure puts "ensured" end puts "exiting at end" exit In this case the output is: raising rescued ensured exiting at end which is pretty predictable. We raised before going into go_away, so the exception was handled, _ensured_ and finally the program exited naturally at the end. Now the same with the unnatural exit at go_away (we just comment the code raising an exception away): def go_away puts "exiting at go_away" exit end begin # puts "raising" # raise puts "going" go_away puts "went" rescue puts "rescued" ensure puts "ensured" end puts "exiting at end" exit The output is radically different: going exiting at go_away ensured Now, imagine your f.close was in place of 'puts "exiting at end"'. It would not have get called (even thought the file would have been closed when the clean-up of the object comes when Ruby shuts down). But to get to the heart of the feature we have to modify the rescue clause a little. Let's define the code to rescue all exceptions: def go_away puts "exiting at go_away" exit end begin # puts "raising" # raise puts "going" go_away puts "went" rescue Exception => e puts "rescued " + e.inspect ensure puts "ensured" end puts "exiting at end" exit Now the output is: going exiting at go_away rescued SystemExit ensured exiting at end So the thing is we just had not any rescue part for this type of exception. Now that we have ensure is working along the lines of it. In real life you want to write ensures, as your code communicates the idea you want the file be closed after it has been used and you don't want to go down and write a general exception handler, because it makes your code inflexible, and probably requires a rewrite to handlers logic when you make changes (add new exceptions or change exception hierarcy). If you want to emulate ensure, you have to add bunch of f.close if you want the following code to close files in all rescuing clauses: class MyExit < SystemExit end for error in [Exception, SystemExit, MyExit] begin raise error rescue MyExit => e puts "rescued specifically MyExit: " + e.inspect rescue SystemExit => e puts "rescued specifically SystemExit: " + e.inspect rescue Exception => e puts "rescued generally " + e.inspect ensure puts "ensuring" end end puts "exiting at end" exit outputting: rescued generally# ensuring rescued specifically SystemExit: # ensuring rescued specifically MyExit: # ensuring exiting at end or if you think reordering would help, you end up with even harder problem (as only the general exception handler gets used): class MyExit < SystemExit end for error in [Exception, SystemExit, MyExit] begin raise error rescue Exception => e puts "rescued generally " + e.inspect rescue SystemExit => e puts "rescued specifically SystemExit: " + e.inspect rescue MyExit => e puts "rescued specifically MyExit: " + e.inspect ensure puts "ensuring" end end puts "exiting at end" exit outputting rescued generally # ensuring rescued generally # ensuring rescued generally # ensuring exiting at end Adding some new exception classes afterwards and arranging the code for them becomes trickier when the code, which has to be called in every possible exit of the exception handling, has to be manually found and injected in new handler. So I hope it has became clear why it's beneficial to have a way in exception handling to specify something to be run after which ever exception handler was executed (or wasn't). It's just a short-hand for not repeating yourself, but a very powerful one as it does not only ease up your development but also prepares the code for the future changes while maintains your original message what the code should do to the next developer. - Aleksi