Jump to content

  • Log In with Google      Sign In   
  • Create Account






D Tip: Use Scope Statements in Proper Scope

Posted by Aldacron, in D 03 November 2013 · 1,041 views

d lang
One D construct I often use is the scope guard statement. This allows you to write code that executes when a scope exits under one of three circumstances: an exception is thrown, no exception is thrown, or always. Example:
void main() {
    import std.stdio;

    scope( failure ) {
        writeln( "I only execute when the scope exits due to an exception being thrown.");
    }

    scope( success ) {
        writeln( "I only execute when the scope exits normally." );
    }

    scope( exit ) {
        writeln( "I always execute when the scope exits, exceptions or no." );
    }
}
In this example, as written, two lines will be printed to the console when the function exits, the ones for the exit and success conditions, since no exception is thrown. The exit condition will be written first, since anything scope statements are executed in the reverse order they are declared. If you add a return statement before the success condition or throw an exception before the failure condition, nothing will be printed -- the scope blocks won't be executed because the code never reached the point where they are declared. Also, you can have more than one statement in each scope block. Here's another example.
void main() {
    import std.stdio;

    scope( exit ) {
        writeln( "I'm always going to execute.");
        writeln( "Because I'm the first scope guard in this scope." );
        writeln( "And the scope doesn't exit before I am declared.");
        writeln( "But I will be the last scope guard to run." );
    }

    int x = 5;
    int y = 6;

    scope( success ) writefln( "I won't execute because of the Error thrown below. BTW, x+y = %s",  x + y );

    scope( exit ) {
        writeln( "Yes, multiple scope guards of the same type can be declared." );
        writeln( "And this scope( exit ) will execute before the one above." );
    }

    if( y - x != 0 ) {
        throw new Error( "No scopes declared after me will execute." );
    }

    scope( failure ) 
        writeln( "I won't run, since an exception was thrown before this point." );
    
}
You can run this code online, where you will see this ouput:

Yes, multiple scope guards of the same type can be declared.
And this scope( exit ) will execute before the one above.
I'm always going to execute.
Because I'm the first scope guard in this scope.
And the scope doesn't exit before I am declared.
But I will be the last scope guard to run.
object.Error: No scopes declared after me will execute.


D also has the try...catch...finally construct, which is what scope guard statements are lowered to by the compiler. While this is extremely useful and eliminates the need for try..catch..finally in many cases, it is not a complete replacement. For one thing, if there is an exception thrown, scope( failure ) does not give you access to the exception object. In practice, I've found that I use the exit condition the most, with success a distant second. I find that I rarely need the failure condition, but it's handy when I do.

This feature may not seem like much on the surface, but as soon as you start to use it you'll fall in love with it. I actually feel disappointed when I find I need to replace a scope guard with a manual try...catch block, as it clutters things up. But what you can't do is add them blindly to your code. The above examples only use the scope guards at function level, but you can put them in any scope. Conditional blocks, loops, anonymous scopes, wherever you want. The statements in the scope guard will execute if they meet the conditions when the scope in which they are declared exits. Like this:
void main() {
    import std.stdio;

    if( 1 > 0 ) {
        scope( exit ) writeln( "I'll execute as soon as the if exits.");
    }

    for( size_t i=0; i<3; ++i ) {
        scope( exit ) writeln( "How many times will I execute?" );
    }
    
    scope( exit ) writeln( "Will the above scopes be run before I am? Yes, they will." );    
}
Output:

I'll execute as soon as the if exits.
How many times will I execute?
How many times will I execute?
How many times will I execute?
Will the above scopes be run before I am? Yes, they will.


The scope guards in the if block and the for loop are declared before the last scope, but in this case all three are in separate scopes, so they aren't executed in reverse order. The first one is executed as soon as the if block exits and the second one is executed each time the for loop scope exits (three times in all). Those scopes exit before the final scope guard is every declared, so it executes last, only when the main function exits.

That brings me to the point of this post. You should always be aware of the scope in which your scope guard is declared, what is visible in that scope, and what happens in any scopes that follow it. I had never had any issues with scope guards until today. Can you spot the error?
Config loadConfig( string fileName ) {
    auto path = Paths.findSettingsFilePath( fileName );            
    scope( exit ) path.destroy();

    if( path !is null ) {
        auto config = al_load_config_file( path.toCString() );
        return new Config( config, fileName );
    } else {
        return createConfig( fileName );
    }
}
I'm so used to putting a scope guard right after allocation of a resource that, in this case, I didn't pay attention to how that particular resource is used in the function. In this particular case, it is possible for the path instance to be null. Because I declared the scope guard at function scope, it will always execute, regardless of whether the path instance is null or not. If the path object is null, the result is an access violation.

One way to fix this is to add a null check inside the scope guard:
scope( exit ) if( path !is null ) path.destroy();
But that's rather silly, since there's already an if block that checks for null. Furthermore, since the existing if block also creates a new scope, and only in the case where the path object is non-null, then the better solution is to move the scope guard to the if block like so:
auto path = Paths.findSettingsFilePath( fileName );
if( path !is null ) {
    scope( exit ) path.destroy();
    ...
}
D's scope guards are quite useful, but always be aware of how you're using them. Like anything else in programming, if you aren't paying attention, you can create bugs for yourself that may not manifest at all during development and could be hard to track down if they show up on a user machine. As I used to hear all the time in the Army, "More attention to detail, soldier!"




Nice article.

I have watched a few videos about d and the language looks very interesting, I might have to give it a try.

It's a pleasure to work with. It's the only programming language I've ever really *enjoyed* using. 

July 2014 »

S M T W T F S
  12345
6789101112
13141516171819
20212223242526
272829 30 31  

Recent Entries

Recent Comments

PARTNERS