scope
As we have seen in the previous chapter, expressions that must always be executed are written in the finally block, and expressions that must be executed when there are error conditions are written in catch blocks.
We can make the following observations about the use of these blocks:
catchandfinallycannot be used without atryblock.- Some of the variables that these blocks need may not be accessible within these blocks:
void foo(ref int r) { try { int addend = 42; r += addend; mayThrow(); } catch (Exception exc) { r -= addend; // ← derleme HATASI } }
That function first modifies the reference parameter and then reverts this modification when an exception is thrown. Unfortunately,
addendis accessible only in thetryblock, where it is defined. (Note: This is related to name scopes, as well as object lifetimes, which will be explained in a later chapter.) - Writing all of potentially unrelated expressions in the single
finallyblock at the bottom separates those expressions from the actual code that they are related to.
The scope statements have similar functionality to the catch and finally scopes but they are better in many respects. Like finally, the three different scope statements are about executing expressions when leaving scopes:
scope(exit): the expression is always executed when exiting the scope, regardless of whether successfully or due to an exceptionscope(success): the expression is executed only if the scope is being exited successfullyscope(failure): the expression is executed only if the scope is being exited due to an exception
Although these statements are closely related to exceptions, they can be used without a try-catch block.
As an example, let's write the function above with a scope(failure) statement:
void foo(ref int r) { int addend = 42; r += addend; scope(failure) r -= addend; mayThrow(); }
The scope(failure) statement above ensures that the r -= addend expression will be executed if the function's scope is exited due to an exception. A benefit of scope(failure) is the fact that the expression that reverts another expression is written close to it.
scope statements can be specified as blocks as well:
scope(exit) { // ... expressions ... }
Here is another function that tests all three of these statements:
void test() { scope(exit) writeln("when exiting 1"); scope(success) { writeln("if successful 1"); writeln("if successful 2"); } scope(failure) writeln("if thrown 1"); scope(exit) writeln("when exiting 2"); scope(failure) writeln("if thrown 2"); throwsHalfTheTime(); }
If no exception is thrown, the output of the function includes only the scope(exit) and scope(success) expressions:
when exiting 2 if successful 1 if successful 2 when exiting 1
If an exception is thrown, the output includes the scope(exit) and scope(failure) expressions:
if thrown 2 when exiting 2 if thrown 1 when exiting 1 object.Exception@...: the error message
As seen in the outputs, the blocks of the scope statements are executed in reverse order. This is because later code may depend on previous variables. Executing the scope statements in reverse order enables undoing side effects of earlier expressions in a consistent order.