delegate confusion
bitwise via Digitalmars-d
digitalmars-d at puremagic.com
Fri Aug 4 09:57:37 PDT 2017
I'm confused about how D's lambda capture actually works, and
can't find any clear specification on the issue. I've read the
comments on the bug about what's described below, but I'm still
confused. The conversation there dropped off in 2016, and the
issue hasn't been fixed, despite high bug priority and plenty of
votes.
Consider this code:
void foo() {
void delegate()[] funs;
foreach(i; 0..5)
funs ~= (){ writeln(i); };
foreach(fun; funs)
fun();
}
void bar() {
void delegate()[] funs;
foreach(i; 0..5)
{
int j = i;
funs ~= (){ writeln(j); };
}
foreach(fun; funs)
fun();
}
void delegate() baz() {
int i = 1234;
return (){ writeln(i); };
}
void overwrite() {
int i = 5;
writeln(i);
}
int main(string[] argv)
{
foo();
bar();
auto fn = baz();
overwrite();
fn();
return 0;
}
First, I run `foo`. The output is "4 4 4 4 4".
So I guess `i` is captured by reference, and the second loop in
`foo` works because the stack hasn't unwound, and `i` hasn't been
overwritten, and `i` contains the last value that was assigned to
it.
Next I run `bar`. I get the same output of "4 4 4 4 4". While
this hack works in C#, I suppose it's reasonable to assume the D
compiler would just reuse stack space for `j`, and that the C#
compiler has some special logic built in to handle this.
Now, I test my conclusions above, and run `baz`, `overwrite` and
`fn`. The result? total confusion.
The output is "5" then "1234". So if the lambdas are referencing
the stack, why wasn't 1234 overwritten?
Take a simple C++ program for example:
int* foo() {
int i = 1234;
return &i;
}
void overwrite() {
int i = 5;
printf("%d\n", i);
}
int main()
{
auto a = foo();
overwrite();
printf("%d\n", *a);
return 0;
}
This outputs "5" and "5" which is exactly what I expect, because
I'm overwriting the stack space where the first `i` was stored
with "5".
So now, I'm thinking.... D must be storing these captures on the
heap then..right? So why would I get "4 4 4 4 4" instead of "0 1
2 3 4" for `foo` and `bar`?
This makes absolutely no sense at all.
It seems like there are two straight forward approaches available
here:
1) capture everything by reference, in which case the `overwrite`
example would work just like the C++ version. Then, it would be
up to the programmer to heap allocate anything living beyond the
current scope.
2) heap allocate a chunk of space for each lambda's captures, and
copy everything captured into that space when the lambda is
constructed. This of course, would mean that `foo` and `bar`
would both output "0 1 2 3 4".
When I look at the output I get from the code above though, it
seems like neither of these things were done, and that someone
has gone way out of their way to implement some very strange
behavior.
What I would prefer, would be a mixture of reference and value
capture like C++, where I could explicitly state whether I wanted
(1) or (2). I would settle for (2) though.
While I'm sure there is _some_ reason that things currently work
the way they do, the current behavior is very unintuitive, and
gives no control over how things are captured.
More information about the Digitalmars-d
mailing list