www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - scope(exit) considered harmful

reply Justin Johansson <no spam.com> writes:
Long ago in the days before D, I was used to commenting out code with one of
two idioms:

#if false
.. this code is not compiled
#endif

or

if (false) {
.. this code is compiled but not executed
}

and to turn stuff back on again:

#if true
.. this code is back on now
#endif

or

if (true) {
.. this code is back on now too
}

but in the D world beware the Ides of Scopes because if your curly
brace block with the true/false conditional contains a scope(exit)
you will get explainable but unexpected behaviour if this code
is enabled inside an if(true) block.

Gotcha.

If scope(exit) is meant to be some mechanism for saving try/finally
boiler-plate code, it is a can of worms, otherwise it is a can of
i-dont-know-what-it's-good-for.

To my way of thinking, and no doubt I should go back to remedial CS-101,
semantics of scope(exit)  should related to function scope and not
curly-brace scope-statement scope.

Whilst an experienced coder (C/C++) and well understand what's
happening here in D, I feel compelled to say that this would have
to be a nightmare come true for newcomers to D.

My advice, stick to try/catch/finally and avoid D scope(exit) et. al.

-- Justin Johansson
Nov 08 2009
next sibling parent Jacob Carlborg <doob me.com> writes:
On 11/8/09 11:46, Justin Johansson wrote:
 Long ago in the days before D, I was used to commenting out code with one of
 two idioms:

 #if false
 .. this code is not compiled
 #endif

 or

 if (false) {
 .. this code is compiled but not executed
 }

 and to turn stuff back on again:

 #if true
 .. this code is back on now
 #endif

 or

 if (true) {
 .. this code is back on now too
 }

 but in the D world beware the Ides of Scopes because if your curly
 brace block with the true/false conditional contains a scope(exit)
 you will get explainable but unexpected behaviour if this code
 is enabled inside an if(true) block.

 Gotcha.

 If scope(exit) is meant to be some mechanism for saving try/finally
 boiler-plate code, it is a can of worms, otherwise it is a can of
 i-dont-know-what-it's-good-for.

 To my way of thinking, and no doubt I should go back to remedial CS-101,
 semantics of scope(exit)  should related to function scope and not
 curly-brace scope-statement scope.

 Whilst an experienced coder (C/C++) and well understand what's
 happening here in D, I feel compelled to say that this would have
 to be a nightmare come true for newcomers to D.

 My advice, stick to try/catch/finally and avoid D scope(exit) et. al.

 -- Justin Johansson

The translation of "#if" to D is "static if" which does not introduce a new scope.
Nov 08 2009
prev sibling next sibling parent reply downs <default_357-line yahoo.de> writes:
auto var = hairilyAllocatedObject();
scope(exit) cleanupObject(var);

sock.send("<html><head></head><body>");
scope(exit) sock.send("</body></html>");

logln("Digraph D {");
scope(exit) logln("}");

SDL_LockSurface(display.surface);
scope(exit) SDL_UnlockSurface(display.surface);

auto dg = { performComplexExitCleanupDuty; };
scope(exit) dg();
....
dg = dg /apply/ (proc dg) { moreCleanup; dg(); };
....
/* Half a dozen different ways of leaving the function go here */

auto backup = var; var = newValue;
scope(exit) var = backup;
Nov 08 2009
next sibling parent Daniel Keep <daniel.keep.lists gmail.com> writes:
downs wrote:
 ...

http://stackoverflow.com/questions/1247778/is-ds-scope-failure-success-exit-necessary/1248484#1248484
Nov 08 2009
prev sibling parent reply Justin Johansson <no spam.com> writes:
downs Wrote:

 auto var = hairilyAllocatedObject();
 scope(exit) cleanupObject(var);
 
 sock.send("<html><head></head><body>");
 scope(exit) sock.send("</body></html>");
 
 logln("Digraph D {");
 scope(exit) logln("}");
 
 SDL_LockSurface(display.surface);
 scope(exit) SDL_UnlockSurface(display.surface);
 
 auto dg = { performComplexExitCleanupDuty; };
 scope(exit) dg();
 ....
 dg = dg /apply/ (proc dg) { moreCleanup; dg(); };
 ....
 /* Half a dozen different ways of leaving the function go here */
 
 auto backup = var; var = newValue;
 scope(exit) var = backup;

Yes fine. You demonstrate the cool use of scope(exit) but this is a bit like kidding yourself and conscientiously thinking you didn't *actually* tell a lie because you just withheld information. Conspicuous, by the absence of, in your examples is any use of scope(exit) inside curly-brace scope statements embedded *inside* a function. Your examples look fine if one assumes scope(exit) as relating to function scope.
Nov 08 2009
next sibling parent downs <default_357-line yahoo.de> writes:
Justin Johansson wrote:
 downs Wrote:
 
 auto var = hairilyAllocatedObject();
 scope(exit) cleanupObject(var);

 sock.send("<html><head></head><body>");
 scope(exit) sock.send("</body></html>");

 logln("Digraph D {");
 scope(exit) logln("}");

 SDL_LockSurface(display.surface);
 scope(exit) SDL_UnlockSurface(display.surface);

 auto dg = { performComplexExitCleanupDuty; };
 scope(exit) dg();
 ....
 dg = dg /apply/ (proc dg) { moreCleanup; dg(); };
 ....
 /* Half a dozen different ways of leaving the function go here */

 auto backup = var; var = newValue;
 scope(exit) var = backup;

Yes fine. You demonstrate the cool use of scope(exit) but this is a bit like kidding yourself and conscientiously thinking you didn't *actually* tell a lie because you just withheld information. Conspicuous, by the absence of, in your examples is any use of scope(exit) inside curly-brace scope statements embedded *inside* a function. Your examples look fine if one assumes scope(exit) as relating to function scope.

I assume scope(exit) is relating to whatever scope I'm currently in. I'm not sure how my examples oppose that. When I do scope(exit) in a while loop, I expect it to be run at the end of the loop, not at the end of the function. Don't blame the language for your misunderstanding of it.
Nov 08 2009
prev sibling parent BCS <none anon.com> writes:
Hello Justin,

 Your examples look fine if one assumes scope(exit) as relating to
 function scope.

If you expect scope(exit) to trigger on exiting the function you will be in error. It's called /scope/ exit because it triggers on exit from ANY scope.
Nov 08 2009
prev sibling next sibling parent KennyTM~ <kennytm gmail.com> writes:
On Nov 8, 09 18:46, Justin Johansson wrote:
 Long ago in the days before D, I was used to commenting out code with one of
 two idioms:

 #if false
 .. this code is not compiled
 #endif

 or

 if (false) {
 .. this code is compiled but not executed
 }

 and to turn stuff back on again:

 #if true
 .. this code is back on now
 #endif

 or

 if (true) {
 .. this code is back on now too
 }

 but in the D world beware the Ides of Scopes because if your curly
 brace block with the true/false conditional contains a scope(exit)
 you will get explainable but unexpected behaviour if this code
 is enabled inside an if(true) block.

 Gotcha.

 If scope(exit) is meant to be some mechanism for saving try/finally
 boiler-plate code, it is a can of worms, otherwise it is a can of
 i-dont-know-what-it's-good-for.

 To my way of thinking, and no doubt I should go back to remedial CS-101,
 semantics of scope(exit)  should related to function scope and not
 curly-brace scope-statement scope.

 Whilst an experienced coder (C/C++) and well understand what's
 happening here in D, I feel compelled to say that this would have
 to be a nightmare come true for newcomers to D.

 My advice, stick to try/catch/finally and avoid D scope(exit) et. al.

 -- Justin Johansson

Use static if, debug, version, or /+ ... +/.
Nov 08 2009
prev sibling next sibling parent reply Ellery Newcomer <ellery-newcomer utulsa.edu> writes:
Justin Johansson wrote:
 but in the D world beware the Ides of Scopes because if your curly
 brace block with the true/false conditional contains a scope(exit)
 you will get explainable but unexpected behaviour if this code
 is enabled inside an if(true) block.

Elaboration?
Nov 08 2009
next sibling parent reply Justin Johansson <no spam.com> writes:
Ellery Newcomer Wrote:

 Justin Johansson wrote:
 but in the D world beware the Ides of Scopes because if your curly
 brace block with the true/false conditional contains a scope(exit)
 you will get explainable but unexpected behaviour if this code
 is enabled inside an if(true) block.

Elaboration?

Thanks for asking. Sorry for delay in reply due my timezone. Currently 5 am and waked due to heat wave in my neck of the woods. I've stripped my dev code down to basics to demonstrate what happened to me. (This is from my scripting language project being developed in D). // (Replace auto with proper typename to make things clearer for the example.) Expr compile( string statement) { // compile statement to some "expression" node // note that this method might throw a syntax error Expr expr = ... return expr; } int compileAndExecute( Expr someOtherExpr, string statement, int delegate( Expr expr) apply) { // push someOtherExpr onto some internal stack; // compile new expression node and execute result // via the apply callback; pop the internal stack // when done. // Use try/finally back to ensure internal stack is // popped in case compile throws an exception try { pushExpr( someOtherExpr); Expr newExpr = compile( statement); return apply( newExpr); } finally { popExpr(); } } // Hey, let's tidy this up with D's you-beaut scope(exit) thing. int compileAndExecute( Expr someOtherExpr, string statement, int delegate( Expr expr) apply) { // push someOtherExpr onto some internal stack; // compile new expression node and execute result // via the apply callback; pop the internal stack // when done (guarding against compile exception) pushExpr( someOtherExpr); scope(exit) popExpr(); Expr newExpr = compile( statement); return apply( newExpr); } // Cool, I think I like this better. Let's test it. // Okay we know apply is meant to fail if we don't do // the push/pop thing. Test by commenting out. int compileAndExecute( Expr someOtherExpr, string statement, int delegate( Expr expr) apply) { //pushExpr( someOtherExpr); //scope(exit) popExpr(); Expr newExpr = compile( statement); return apply( newExpr); } // Yep, apply failed as expected. Play around for a // bit longer turning the push/pop thing off and on. // Note used /+ +/ this time. int compileAndExecute( Expr someOtherExpr, string statement, int delegate( Expr expr) apply) { /+ pushExpr( someOtherExpr); scope(exit) popExpr(); +/ Expr newExpr = compile( statement); return apply( newExpr); } // Yep, all good. Play around for a bit longer turning // the push/pop thing off and on. Getting sick of hitting // two lines to comment out. try if (false) instead -- // then just one line to hit. int compileAndExecute( Expr someOtherExpr, string statement, int delegate( Expr expr) apply) { if (false) { pushExpr( someOtherExpr); scope(exit) popExpr(); } Expr newExpr = compile( statement); return apply( newExpr); } // Yep, all good. Failures working as expected. // Turn the push/pop thing back on. Leave the if // statement in place for moment just in case we // wanna mess around more later. int compileAndExecute( Expr someOtherExpr, string statement, int delegate( Expr expr) apply) { if (true) { pushExpr( someOtherExpr); scope(exit) popExpr(); } Expr newExpr = compile( statement); return apply( newExpr); } // What the F*&%$~? This doesn't work any more. It used too. // After some ***lengthy*** amount of time wasted trying to figure this out. // ... int compileAndExecute( Expr someOtherExpr, string statement, int delegate( Expr expr) apply) { pushExpr( someOtherExpr); scope(exit) popExpr(); Expr newExpr = compile( statement); return apply( newExpr); } // Works again. Penny drops -- scope(exit) activates // when leaving the if (true) {...} statement and not at // function scope. True the D doco says that; Guess I // should have used a static if for testing. int compileAndExecute( Expr someOtherExpr, string statement, int delegate( Expr expr) apply) { static if (true) { pushExpr( someOtherExpr); scope(exit) popExpr(); } Expr newExpr = compile( statement); return apply( newExpr); } Can you understand my frustration? -- Justin
Nov 08 2009
parent reply Justin Johansson <no spam.com> writes:
Bill Baxter Wrote:

 On Sun, Nov 8, 2009 at 10:44 AM, Justin Johansson <no spam.com> wrote:
 
 Can you understand my frustration?

Yes, I can certainly understand that it's difficult to use scope() constructs when you haven't understood what a scope is. But that's not much of an argument against them. The same rant you just wrote would apply equally to C++ RAII objects. They won't work properly either if you haven't understood what this means: "destructors are called upon exiting the scope in which declared". --bb

True you have to understand a tool to use it properly. In the C++ RAII situation, I would have spotted my mistake straight away. // C++ if (true) { Foo foo; } Error foo goes out of scope when if block is exited. Acknowledge this is what happens with D's scope(exit) as well though to me it doesn't seem as intuitive as its C++ counterpart. Just thinking about how C's atexit() function reflects "process-scope", I'm just wondering if it would be a good idea if the D scope statement could be extended to other types of scope, function scope in particular and not just curly-brace block scope. What do you think? Justin
Nov 08 2009
parent Walter Bright <newshound1 digitalmars.com> writes:
Justin Johansson wrote:
 Acknowledge this is what happens with D's scope(exit)
 as well though to me it doesn't seem as intuitive as
 its C++ counterpart.

It does say "scope" exit, not function exit. I think it is intuitive.
Nov 08 2009
prev sibling parent Bill Baxter <wbaxter gmail.com> writes:
On Sun, Nov 8, 2009 at 10:44 AM, Justin Johansson <no spam.com> wrote:

 Can you understand my frustration?

Yes, I can certainly understand that it's difficult to use scope() constructs when you haven't understood what a scope is. But that's not much of an argument against them. The same rant you just wrote would apply equally to C++ RAII objects. They won't work properly either if you haven't understood what this means: "destructors are called upon exiting the scope in which declared". --bb
Nov 08 2009
prev sibling next sibling parent reply dsimcha <dsimcha yahoo.com> writes:
Hey, I never programmed at all seriously in C++ before coming to D and I somehow
figured out scope(exit).  I'm not even sure you'd be able to pry scope(exit) out
of my cold, dead hands.  I might super-glue it to my hands on my death bed.

Example of where you could do stuff with scope statements that would be **almost
impossible** with try/catch/finally:

enum myConvenientMixin =
q{
    myRegionAllocator.init();
    scope(exit) myRegionAllocator.destroy();
};

void someFunction() {
    mixin(myConvenientMixin);

    // Build some really complicated data structure using
    // myRegionAllocator.
}

With finally only, myRegionAllocator would be so horribly ugly to use in the
presence of throwing code that noone would ever even consider doing such a
thing.
 With scope(exit) and mixins, you've solved the problem of the memory getting
freed in one line of code and don't even need to think about it when writing the
rest of your function.
Nov 08 2009
next sibling parent reply Bill Baxter <wbaxter gmail.com> writes:
On Sun, Nov 8, 2009 at 8:12 AM, dsimcha <dsimcha yahoo.com> wrote:
 Hey, I never programmed at all seriously in C++ before coming to D and I =

 figured out scope(exit). =A0I'm not even sure you'd be able to pry scope(=

 of my cold, dead hands. =A0I might super-glue it to my hands on my death =

 Example of where you could do stuff with scope statements that would be *=

 impossible** with try/catch/finally:

 enum myConvenientMixin =3D
 q{
 =A0 =A0myRegionAllocator.init();
 =A0 =A0scope(exit) myRegionAllocator.destroy();
 };

 void someFunction() {
 =A0 =A0mixin(myConvenientMixin);

 =A0 =A0// Build some really complicated data structure using
 =A0 =A0// myRegionAllocator.
 }

 With finally only, myRegionAllocator would be so horribly ugly to use in =

 presence of throwing code that noone would ever even consider doing such =

 =A0With scope(exit) and mixins, you've solved the problem of the memory g=

 freed in one line of code and don't even need to think about it when writ=

 rest of your function.

Well, in C++ you'd probably wrap up myRegionAllocator in a struct and put the .destroy call in its destructor. For use you'd put an instance of the struct on the stack. But scope(exit) is much more convenient than having to put all your cleanup code into a struct. --bb
Nov 08 2009
parent Walter Bright <newshound1 digitalmars.com> writes:
Bill Baxter wrote:
 Well, in C++ you'd probably wrap up myRegionAllocator in a struct and
 put the .destroy call in its destructor.  For use you'd put an
 instance of the struct on the stack.  But scope(exit) is much more
 convenient than having to put all your cleanup code into a struct.

Everything you can do with scope, you can do with C++ RAII objects. But that's like saying everything you can do with C++ virtual functions you can do in C using tables of function pointers. Also, everything you can do with scope you can do with some combination of try/catch/finally. In fact, this is demonstrably true because the D front end rewrites all the scope statements into try/catch/finally. But like doing OOP in C with function pointer tables, who would want to?
Nov 08 2009
prev sibling parent reply Justin Johansson <no spam.com> writes:
dsimcha Wrote:

 Hey, I never programmed at all seriously in C++ before coming to D and I
somehow
 figured out scope(exit).  I'm not even sure you'd be able to pry scope(exit)
out
 of my cold, dead hands.  I might super-glue it to my hands on my death bed.
 
 Example of where you could do stuff with scope statements that would be
**almost
 impossible** with try/catch/finally:
 
 enum myConvenientMixin =
 q{
     myRegionAllocator.init();
     scope(exit) myRegionAllocator.destroy();
 };
 
 void someFunction() {
     mixin(myConvenientMixin);
 
     // Build some really complicated data structure using
     // myRegionAllocator.
 }
 
 With finally only, myRegionAllocator would be so horribly ugly to use in the
 presence of throwing code that noone would ever even consider doing such a
thing.
  With scope(exit) and mixins, you've solved the problem of the memory getting
 freed in one line of code and don't even need to think about it when writing
the
 rest of your function.

I know what you mean, but it's not like "D" stands for "Discovered America". scope(exit) is just some syntactic sugar which stack allocated objects in C++ have been able to do since Bjarne Stroustrup (C++ inventor) was in knee-high locks, umm, socks :-) class Lock { Foo foo; Lock( Foo aFoo): foo( aFoo) { // lock Foo resource lock( foo); } ~Lock() { // unlock Foo resource unlock( foo); } } void myFunc( Foo foo) { Lock( foo); // do something with the locked resource // that might throw an exception // ... processing() // Note that the Foo resource is acquired and // upon entry to this function and released // when the function scope exits irrespective // of whether an exception is thown by // processing() or not. } No sign of any ugly try/finally here; yet achieves safe outcome. C++ constructors/destructors and stack-unwinding mechanism are considered FREAKING AWESOME. -- Justin :-)
Nov 08 2009
next sibling parent reply Walter Bright <newshound1 digitalmars.com> writes:
Justin Johansson wrote:
 C++ constructors/destructors and stack-unwinding mechanism
 are considered FREAKING AWESOME.

Ok, good. Now write (using C++ RAII) the following where both transactions must succeed or neither: void foo() { A(); scope (failure) undo_A(); B(); scope (failure) undo_B(); writeln("A and B succeeded!"); }
Nov 08 2009
parent reply Justin Johansson <no spam.com> writes:
Walter Bright Wrote:

 Justin Johansson wrote:
 C++ constructors/destructors and stack-unwinding mechanism
 are considered FREAKING AWESOME.

Ok, good. Now write (using C++ RAII) the following where both transactions must succeed or neither: void foo() { A(); scope (failure) undo_A(); B(); scope (failure) undo_B(); writeln("A and B succeeded!"); }

Thanks Walter; point taken. Glad we had this discussion :-) -- Justin
Nov 08 2009
parent Walter Bright <newshound1 digitalmars.com> writes:
Justin Johansson wrote:
 Walter Bright Wrote:
 
 Justin Johansson wrote:
 C++ constructors/destructors and stack-unwinding mechanism
 are considered FREAKING AWESOME.

transactions must succeed or neither: void foo() { A(); scope (failure) undo_A(); B(); scope (failure) undo_B(); writeln("A and B succeeded!"); }

Thanks Walter; point taken. Glad we had this discussion :-)

No prob. It's hard to see the benefits of scope vs RAII vs try-catch-finally with only one thing that needs unwinding. But as soon as two or more are added, watch out!
Nov 08 2009
prev sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
Justin Johansson wrote:
 dsimcha Wrote:
 
 Hey, I never programmed at all seriously in C++ before coming to D and I
somehow
 figured out scope(exit).  I'm not even sure you'd be able to pry scope(exit)
out
 of my cold, dead hands.  I might super-glue it to my hands on my death bed.

 Example of where you could do stuff with scope statements that would be
**almost
 impossible** with try/catch/finally:

 enum myConvenientMixin =
 q{
     myRegionAllocator.init();
     scope(exit) myRegionAllocator.destroy();
 };

 void someFunction() {
     mixin(myConvenientMixin);

     // Build some really complicated data structure using
     // myRegionAllocator.
 }

 With finally only, myRegionAllocator would be so horribly ugly to use in the
 presence of throwing code that noone would ever even consider doing such a
thing.
  With scope(exit) and mixins, you've solved the problem of the memory getting
 freed in one line of code and don't even need to think about it when writing
the
 rest of your function.

I know what you mean, but it's not like "D" stands for "Discovered America". scope(exit) is just some syntactic sugar which stack allocated objects in C++ have been able to do since Bjarne Stroustrup (C++ inventor) was in knee-high locks, umm, socks :-) class Lock { Foo foo; Lock( Foo aFoo): foo( aFoo) { // lock Foo resource lock( foo); } ~Lock() { // unlock Foo resource unlock( foo); } } void myFunc( Foo foo) { Lock( foo); // do something with the locked resource // that might throw an exception // ... processing() // Note that the Foo resource is acquired and // upon entry to this function and released // when the function scope exits irrespective // of whether an exception is thown by // processing() or not. } No sign of any ugly try/finally here; yet achieves safe outcome. C++ constructors/destructors and stack-unwinding mechanism are considered FREAKING AWESOME. -- Justin :-)

I can't process how this is great and scope(exit) isn't. For sure, putting a Lock inside an if (true) statement will make the destructor get called at the scope's exit (let me emphasize: *scope's exit*), not function's exit. That is, in your rant you could easily replace scope(exit) with scoped objects and get the same frustration. Andrei
Nov 08 2009
parent Justin Johansson <no spam.com> writes:
Andrei Alexandrescu Wrote:

 Justin Johansson wrote:
 dsimcha Wrote:
 
 Hey, I never programmed at all seriously in C++ before coming to D and I
somehow
 figured out scope(exit).  I'm not even sure you'd be able to pry scope(exit)
out
 of my cold, dead hands.  I might super-glue it to my hands on my death bed.

 Example of where you could do stuff with scope statements that would be
**almost
 impossible** with try/catch/finally:

 enum myConvenientMixin =
 q{
     myRegionAllocator.init();
     scope(exit) myRegionAllocator.destroy();
 };

 void someFunction() {
     mixin(myConvenientMixin);

     // Build some really complicated data structure using
     // myRegionAllocator.
 }

 With finally only, myRegionAllocator would be so horribly ugly to use in the
 presence of throwing code that noone would ever even consider doing such a
thing.
  With scope(exit) and mixins, you've solved the problem of the memory getting
 freed in one line of code and don't even need to think about it when writing
the
 rest of your function.

I know what you mean, but it's not like "D" stands for "Discovered America". scope(exit) is just some syntactic sugar which stack allocated objects in C++ have been able to do since Bjarne Stroustrup (C++ inventor) was in knee-high locks, umm, socks :-) class Lock { Foo foo; Lock( Foo aFoo): foo( aFoo) { // lock Foo resource lock( foo); } ~Lock() { // unlock Foo resource unlock( foo); } } void myFunc( Foo foo) { Lock( foo); // do something with the locked resource // that might throw an exception // ... processing() // Note that the Foo resource is acquired and // upon entry to this function and released // when the function scope exits irrespective // of whether an exception is thown by // processing() or not. } No sign of any ugly try/finally here; yet achieves safe outcome. C++ constructors/destructors and stack-unwinding mechanism are considered FREAKING AWESOME. -- Justin :-)


 I can't process how this is great and scope(exit) isn't. For sure, 
 putting a Lock inside an if (true) statement will make the destructor 
 get called at the scope's exit (let me emphasize: *scope's exit*), not 
 function's exit. That is, in your rant you could easily replace 
 scope(exit) with scoped objects and get the same frustration.
 
 Andrei

"I can't process how this is great and scope(exit) isn't" Be reassured that I am asking myself the same question as to why I feel/felt this way given that scoped objects and scope(exit) can be used for similar purposes. I'm not trying to "get out of this one" but believe me that my honest intuition* is counter to my knowledge of how scope(exit) works so I will try to tease this out. *That intuition is that scope(exit) works at function scope as opposed to block scope (and of course intuition is allowed to be wrong). About 1 week after getting into D (some 2 months ago), it became second nature to me to set up a scope(exit) upon entry into a function in cases which would otherwise requiring try/finally. After a while the pattern set in and I think that perhaps I stopped thinking about it meaning curly-brace block scope and function scope instead. This is reinforced by the fact that I had never used scope(exit) in a nested block (such as an if statement) before this incident .. which obviously turned out to be my mistake. It is possible that I formed a mental image of scope(exit) as being analogous to C's atexit()** function which operates at "process-scope". (**For those that don't know C, atexit() takes a function pointer parameter and effectively schedules a call to this function when the application/process ends.) The other reason I think that I think that scoped objects and scope(exit) "feel" different is that scoped objects (in D) or stack-allocated objects in C++ (RAII) is that the former are "declarative" in nature whereas scope(exit) feels more "instructive" .. i.e. instruct the program to take this course of action when something finishes (block scope exit in this case). In the former case, the (constructor/destructor) behaviour is virtually a side-effect of, or implicit in, the declaration. There is nothing implicit about scope(exit). It usage is making an explicit statement. Therefore I think that it is a reasonable hypothesis that models may well arise in the minds of users that view the two constructs differently. Lastly consider this situation for which the D solution is, in my opinion, counter-intuitive. You have a function that takes two nullable "lockable-resource" parameters. The requirement is to lock/unlock the respective resources if they are non-null before doing some processing. My intuitive solution as follows is incorrect as it assumes function scope for the envoking the exit action: void foo( Resource res1, Resource res2) { if (res1 !is null) { lock( res1); scope(exit) unlock( res1); } if (res2 !is null) { lock( res2); scope(exit) unlock( res2); } // do some processing given the resources // are now appropriately locked and will // be released when done with // ... } I think the correct solution using D's scope(exit) should be more like: void foo( Resource res1, Resource res2) { if (res1 !is null) lock( res1); scope(exit) if (res1 !is null) unlock( res1); if (res2 !is null) lock( res2); scope(exit) if (res2 !is null) unlock( res2); // do some processing given the resources // are now appropriately locked and will // be released when done with // ... } Assuming that the latter solution is indeed correct for the given problem scenario, I maintain that it doesn't seem intuitive and that there is a valid use case for some hypothetical construct like function-scope(exit) or scope(function-exit) Maybe I'm on a different planet but nevertheless look forward to yr comments. -- Justin Johansson
Nov 08 2009
prev sibling next sibling parent reply Walter Bright <newshound1 digitalmars.com> writes:
Justin Johansson wrote:
 Long ago in the days before D, I was used to commenting out code with one of
 two idioms:
 
 #if false
 .. this code is not compiled
 #endif
 
 or
 
 if (false) {
 .. this code is compiled but not executed
 }
 
 and to turn stuff back on again:
 
 #if true
 .. this code is back on now
 #endif
 
 or
 
 if (true) {
 .. this code is back on now too
 }
 
 but in the D world beware the Ides of Scopes because if your curly
 brace block with the true/false conditional contains a scope(exit)
 you will get explainable but unexpected behaviour if this code
 is enabled inside an if(true) block.
 
 Gotcha.

This is incorrect. Given: if (false) { scope(exit) writeln("hello world!"); } The writeln will never execute.
Nov 08 2009
next sibling parent reply Justin Johansson <no spam.com> writes:
Walter Bright Wrote:

 Justin Johansson wrote:
 Long ago in the days before D, I was used to commenting out code with one of
 two idioms:
 
 #if false
 .. this code is not compiled
 #endif
 
 or
 
 if (false) {
 .. this code is compiled but not executed
 }
 
 and to turn stuff back on again:
 
 #if true
 .. this code is back on now
 #endif
 
 or
 
 if (true) {
 .. this code is back on now too
 }
 
 but in the D world beware the Ides of Scopes because if your curly
 brace block with the true/false conditional contains a scope(exit)
 you will get explainable but unexpected behaviour if this code
 is enabled inside an if(true) block.
 
 Gotcha.

This is incorrect. Given: if (false) { scope(exit) writeln("hello world!"); } The writeln will never execute.

Incorrect? Hud? I didn't say that I expected it to execute? I said if (false) { // .. this code is compiled but ***not*** executed } which was my desired behaviour.
Nov 08 2009
parent Walter Bright <newshound1 digitalmars.com> writes:
Justin Johansson wrote:
 which was my desired behaviour.

I guess I don't understand what the gotcha is, then. Can you be explicit?
Nov 08 2009
prev sibling parent Don <nospam nospam.com> writes:
Walter Bright wrote:
 Justin Johansson wrote:
 Long ago in the days before D, I was used to commenting out code with 
 one of
 two idioms:

 #if false
 .. this code is not compiled
 #endif

 or

 if (false) {
 .. this code is compiled but not executed
 }

 and to turn stuff back on again:

 #if true
 .. this code is back on now
 #endif

 or

 if (true) {
 .. this code is back on now too
 }

 but in the D world beware the Ides of Scopes because if your curly
 brace block with the true/false conditional contains a scope(exit)
 you will get explainable but unexpected behaviour if this code
 is enabled inside an if(true) block.

 Gotcha.

This is incorrect. Given: if (false) { scope(exit) writeln("hello world!"); } The writeln will never execute.

if (true) scope (exit) writeln("hello world!"); // doesn't execute either! -- bugzilla 1894, see also 3323 and 1087. Probably not good to encourage people to use these right now :-(.
Nov 08 2009
prev sibling parent Lutger <lutger.blijdestijn gmail.com> writes:
Justin Johansson wrote:

...
 If scope(exit) is meant to be some mechanism for saving try/finally
 boiler-plate code, it is a can of worms, otherwise it is a can of
 i-dont-know-what-it's-good-for.

It is that, but also used where you could otherwise use RAII a la C++.
 To my way of thinking, and no doubt I should go back to remedial CS-101,
 semantics of scope(exit)  should related to function scope and not
 curly-brace scope-statement scope.

I understand this could possible be tricky but consider this: - it is in the docs - the name says it almost literally: scope(exit) means: do this at scope exit. - it behaves the same as try/catch/finally (which introduces a new scope) and destructors in C++. It is also more expressive than it would be were scope be restricted to function scope. I can't come up with a real example, but suppose this: A get_A_or_null() { auto a = null; if (takeThisBranch) { a = new A; scope(failure) a.cleanup; // segfaults! auto b = new B; scope(exit) b.cleanup(); // Undefined behaviour or compiler error? } return a; }
Nov 08 2009