On 09/02/13 00:27, Joseph Rushton Wakeling wrote: > Hello all, > > I came across an interesting little pitfall of delegates recently which I > thought I'd share. > > TL;DR: having reference types as struct members can be dangerous. ;-) > > Suppose we have a fairly simple range which is supposed to count from 0 to > some maximum. The trick is that the count is implemented by a function > that's accessed by a delegate. > > struct Foo > { > private size_t _count = 0, _max; > private void delegate() jump = null; > > @disable this(); > > this(size_t max) > { > _max = max; > jump = &jump10; > } > > bool empty() @property @safe pure const nothrow > { > return (_count > _max); > } > > size_t front() > { > return _count; > } > > void popFront() > { > if (empty) > { > return; > } > > writeln("At start of popFront(), count = ", _count); > writeln("Jumping ..."); > jump(); > writeln("At end of popFront(), count = ", _count); > } > > void jump10() > { > _count += 10; > writeln("At end of jump, count = ", _count); > } > } > > Now let's put this struct to use: > > auto foo = Foo(50); > > foreach (f; foo) > { > writeln(f); > } > > What we expect is that the program will print out 0, 10, 20, 30, 40, 50 (on > new lines) and exit. > > In fact, the foreach () loop never exits and the logging writeln's we put in > tell us a strange story:
> ... and so on. It seems like the value of _count is never being incremented. > The logging function inside jump10 keeps telling us: "At end of jump, count = > 70" (and 80, and 90, and ... 30470 ... and ...), but front() and popFront() > keep telling us the count is zero. > > Of course, it's because of the delegate. The foreach () loop has taken a > (value-type) copy of the Foo struct, but the delegate jump() is a reference > -- which is pointing to the function jump10 in the _original_ Foo, not the > copy that foreach () is operating on. > > Hence, it's the original's _count that's being incremented, and not that in > foreach () loop's copy -- and so the foreach loop never exits. this(this) { jump.ptr = &this; } While this may not seem ideal, sometimes you do *not* want to do it (when you need to keep the original context), so there are no obvious alternatives that would handle all cases automagically. artur