tags:

views:

407

answers:

5

When using a language that has try/catch/finally is D's failure/success/exit scope statements still useful? D doesnt seem to have finally which may explain why those statements are using in D. But with a language like C# is it useful? I am designing a language so if i see many pros i'll add it in.

+7  A: 

try/catch/finally forces a level of nesting; scope guards don't. Besides, they let you write cleanup code in the same "area" as allocation code, so no more "open file, scroll to end of function, close file, scroll to top of function".

Fundamentally though, it's just a more convenient expression of try/catch/finally exception handling - anything you can do with try/catch/finally you can do with scope guards, and reverse.

Is it worth it? I'm a D fanboy (so, biased), but I'd say definitely.

FeepingCreature
+3  A: 

Distinguishing failure-exit from success-exit is quite useful some of the time -- I have no real world experience with D, but Python's with statement also allows that, and I find it very useful, for example, to either commit or rollback a DB transaction that was opened in the protected part of the body.

When I explained this then-new Python feature (it's been around for a while now;-) to friends and colleagues who are gurus in C++ and Java I found they immediately understood, and saw the interest in having such a feature (Python does have finally, too, but that's no help in distinguishing success from failure, just like in other languages [or C++'s "RAII destruction of auto variables in the block" equivalent]).

Alex Martelli
I dont understand with. I read this http://effbot.org/zone/python-with-statement.htm It looks like it allows the ctor and dtor to run. Doesnt it run without the statement? i dont know python well or what is actually happening
acidzombie24
Point is, that the context manager's `__exit__` special method is called with information about what exception is being propagated, if any; so, a context manager for DB operations, for example, can distinguish between success cases, warranting a DB commit, and failures, requiring a DB rollback. You're talking about a completely different issue -- dtors, per se, could be run in a try/finally, that's just syntactically clumsier than RAII (look THAT up!-) but functionally equivalent. But distinguishing success from failure, in some cases, is CRUCIAL!
Alex Martelli
+5  A: 

Disclaimer I'm a D fan boy too.

someRiskyFunctionThatMayThrow();
lock();
/* we have definitly got the lock so lets active
a piece of code for exit */
scope(exit)
    freelock();

Compared to:

try
{
    someRiskyFunctionThatMayThrow();
    lock();
}
finally
{
    freeLockIfNotGot();
}
Tim Matthews
+16  A: 

scope(X) isn't necessary in the same way that for isn't necessary provided you have if and goto.

Here's a paraphrased example from some code I've been writing today:

sqlite3* db;
sqlite3_open("some.db", &db);
scope(exit) sqlite3_close(db);

sqlite3_stmt* stmt;
sqlite3_prepare_v2(db, "SELECT * FROM foo;", &stmt);
scope(exit) sqlite3_finalize(stmt);

// Lots of stuff...

scope(failure) rollback_to(current_state);
make_changes_with(stmt);

// More stuff...

return;

Contrast this to using try/catch:

sqlite3* db;
sqlite3_open("some.db", &db);
try
{
    sqlite3_stmt* stmt;
    sqlite3_prepare_v2(db, "SELECT * FROM foo;", &stmt);
    try
    {
        // Lots of stuff...
        try
        {
            make_changes_with(stmt);

            // More stuff...
        }
        catch( Exception e )
        {
            rollback_to(current_state);
            throw;
        }
    }
    finally
    {
        sqlite3_finalize(stmt);
    }
}
finally
{
    sqlite3_close(db);
}

The code has turned into spaghetti, spreading the error recovery all over the shop and forcing a level of indentation for every try block. The version using scope(X) is, in my opinion, significantly more readable and easier to understand.

DK
Gold, not only because you should me how it can become spaghetti, but also how the code is so function specific that destuctors should/can not be a solution.
acidzombie24
That first code example is very nice. It's a breath of fresh air. We're all so used to reading the second translated form that the first looks weird, but it has the same confidence and progression that "early return" or "early throw" code does: consider problem A, state the resolution, forget about A and move on to consider problem B, state the resolution, forget about B and move on.
seh
+1  A: 

@DK, It should be pointed out, in C++ (and Java I think) you could easily use an "anonymous" class to accomplish the same thing as scope(exit):

int some_func() 
{
    class _dbguard { sqlite3* db;
                     _dbguard(const _dbguard&); _dbguard& operator=(const _dbguard&);
                 public:
                     _dbguard(const char* dbname) { sqlite3_open(dbname, &db);}
                     ~_dbguard() {sqlite3_close(db);} 
                     operator sqlite3*() { return db; } 

    } db("dbname");
    ...
}

And if you did this more than once you'd immediately turn it into a full class to handle your RAII for you. It is so simple to write I can't imagine a C++ program that uses sqlite (as used in the example) without creating classes like CSqlite_DB and CSqlite_Stmt. In fact the operator sqlite3*() should be anathama and the full version would just have methods that provide statements:

class CSqlite3_DB {
    ...
    CSqlite3_Stmt Prepare(const std::string& sql) {
        sqlite3_stmt* stmt = 0;
        try {
             sqlite3_prepare_v2(db, sql.c_str(), &stmt);
        } catch (...) {}
        return stmt;
    }
};

As for the original question, I'd say the answer is "not really". Proper respect for DRY would tell you to take those long blocks of try/catch/finally and convert them to separate classes that hide the try/catch parts away from the rest where they can (in the case of scope(failure)) and makes resource management transparent (in the case of scope(exit)).

jmucchiello
excellent answer.
acidzombie24
... although I will point out, scope(exit) is about 200 less characters than either version :-)
Tim Keating
Actually, since writing this I do prefer D's method. When I really thought about scope(failure) and scope(success) I was sold. The RAII way can't duplicate those features without ugly MACRO support. Still, for SqlLite example, a full C++ wrapper class is the "proper" C++ solution.
jmucchiello