[NAME] ALL.dao.tutorial.exception [TITLE] Exception Handling [DESCRIPTION] The handling of exceptions in Dao is very much like the handling of panics in the Go programming language. The basic idea is to implement exception handling codes in a function as deferred code block (see the Deferred Block subsection in dao.tutorial.controls for more information about this), which will be automatically executed when the function exists. In the deferred block, the built-in function recover() can be used to recover a list of exceptions, and handle them properly. Then the execution of the caller of function which defines the deferred blok will be resumed normally. 1 routine Test() 2 { 3 defer { io.writeln( 'recovering:', recover() ) } 4 io.writeln( 'Test(): before panic;' ) 5 panic( 123 ) 6 io.writeln( 'Test(): after panic;' ) 7 } 8 9 io.writeln( 'Before Test();' ) 10 Test() 11 io.writeln( 'After Test();' ) In this example, the built-in function panic() is used to raise an exception. This function can take any value as a parameter. If the parameter is an exception object,this exception will be raised; otherwise, an generic exception object will be created to hold the parameter value, and then the created exception object will be raised. Each function can define multiple deferred blocks, but only the call to recover() in the lastly executed deferred block will return the exceptions. An exception type can be passed to recover() to request a specific type of exception. 1 # Example to handle user defined exception type: 2 class MyError : Exception::Error 3 { 4 routine serialize(){ return ('MyError', self) } 5 } 6 7 routine Test() 8 { 9 defer { 10 io.writeln( 'recovering from', recover( MyError ) ) 11 } 12 13 io.writeln( 'Test(): before panic;' ) 14 panic( MyError() ); 15 io.writeln( 'Test(): after panic;' ) 16 } 17 Test() As mentioned above, the normal execution will be resumed by the caller of the function that handles the exceptions in its deferred blocks. In order to resume the normal execution right after handling of exceptions, a special type of code blocks can be used to "host" the deferred blocks, 1 frame { block } 2 frame ( value ) { block } These kinds of blocks are executed in new stack frames, and when these blocks are finished and the frames are removed from the stack, deferred blocks encounted in these blocks will be automatically executed in the same way as the deferred blocks defined in normal functions. If the exceptions are recovered in these deferred blocks, the normal execution will be resumed right after these frame{} blocks, 1 fout = io::stdio 2 3 frame { 4 defer { recover() } 5 fout = io.open( "NonExistentFile.txt", 'r+' ) 6 } 7 8 if( fout != io::stdio ) defer{ fout.close() } 9 10 fout.writeln( 'hello' ) Such frame{} blocks are actually expressions that may return values. The only difference between frame{block} and frame(value){block} is that, in the second case, if the block raises an exception, the default value is returned instead, and the exception is suppressed. This can make codes such as shown in the above example simpler, 1 fout = frame( io::stdio ){ io.open( "NonExistentFile.txt", 'r+' ) } 2 if( fout != io::stdio ) defer{ fout.close() } 3 fout.writeln( 'hello' )