September 06, 2006
I understand that delegates used as functions with closures become invalid when the function enclosing the definition exists. A delegate that outlives the function where it was created can be a bad thing for this reason.

The thing is that this is a silent error. There are no compiler errors or warnings, and there is no exception when the delegate is called. It just silently uses a dangling pointer - the invalidity of the pointer can't be detected since it still points into a vaguely valid looking location (the stack).

As a rule, I have no big problems with this. D is a systems programming language, there are sometimes trade-offs between efficiency and safety, and it is down to the programmer to take responsibility.

BUT - I still want a pattern which will let me detect errors in some cases, either as debug code or in cases where I have to trust a delegate to a library which could concievably keep it alive to long.

One possibility is to use an inner (maybe anonymous) class, and return a delegate to a member function of that. This would give a 'genuine' closure - copying in the needed variables as members. So long as only the member copies were used in the delegated member itself, this is safe - the instance is stored on the heap and garbage collection only happens when all references (delegate included) are dead.

Trouble is, it is also inefficient - presumably the reason why D doesn't have genuine closures for inner functions already. A lot of the time, that extra overhead just isn't needed.

One idea I came up with which works is using a static flag and a scope statement, roughly as follows (not tested code)...


void Make_Delegate (out int delegate () p_Delegate)
{
  static bool l_Delegate_Dead = true;
  int l_Var = 5;

  //...

  int Delegate ()
  {
    if (l_Delegate_Dead)
      throw new Exception ("Dead delegate called.");

    return l_Var;
  }

  p_Delegate = Delegate;
  l_Delegate_Dead = false;
  scope(exit) l_Delegate_Dead = true;

  //...
}

void Test ()
{
  int delegate () l_Delegate;

  Make_Delegate (l_Delegate);

  Writefln ("Result : ", l_Delegate ());  //  throws exception
}


This seems, to me, just complex enough that if I used it a lot, it would be error prone in itself. Too easy to forget the check in the delegate, or to forget the flag setting or the scope statement when needed.

Does anyone have any thoughts on this?