Jump to page: 1 2
Thread overview
[Issue 21929] delegates capture do not respect scoping
May 18, 2021
Vladimir Panteleev
May 18, 2021
Vladimir Panteleev
May 18, 2021
deadalnix
May 18, 2021
Ketmar Dark
May 19, 2021
Walter Bright
May 19, 2021
Walter Bright
May 19, 2021
Walter Bright
May 19, 2021
deadalnix
May 20, 2021
a11e99z
May 20, 2021
deadalnix
Dec 05, 2021
Stanislav Blinov
Feb 26, 2022
Walter Bright
Feb 26, 2022
Walter Bright
May 24, 2022
Tim
May 24, 2022
deadalnix
May 24, 2022
deadalnix
Jul 28, 2022
Walter Bright
Jul 28, 2022
Walter Bright
Jul 28, 2022
deadalnix
Dec 17, 2022
Iain Buclaw
May 18, 2021
https://issues.dlang.org/show_bug.cgi?id=21929

Vladimir Panteleev <dlang-bugzilla@thecybershadow.net> changed:

           What    |Removed                     |Added
----------------------------------------------------------------------------
             Status|NEW                         |RESOLVED
                 CC|                            |dlang-bugzilla@thecybershad
                   |                            |ow.net
         Resolution|---                         |INVALID

--- Comment #1 from Vladimir Panteleev <dlang-bugzilla@thecybershadow.net> ---
In JavaScript:

---------------------------------------------------------------
var dgs = [];

for (var i = 0; i < 10; i++) {
        dgs.push(function() {
                console.log(i);
        });
}

dgs.push(function() {
        console.log("With cached variables");
});

for (var i = 0; i < 10; i++) {
        var index = i;
        dgs.push(function() {
                console.log(index);
        });
}

for (var dg of dgs) {
        dg();
}
---------------------------------------------------------------

Prints:

---------------------------------------------------------------
10
10
10
10
10
10
10
10
10
10
With cached variables
9
9
9
9
9
9
9
9
9
9
---------------------------------------------------------------

So, I don't think D's behavior is unexpected compared to other languages.

Note that if you change "var" to "let" in JS then it behaves closer as to what you may expect.

At this point this is part of the language and is not a bug, so I don't think the bugtracker is the proper place to post this.

The workaround in D is the same as in JavaScript (before "let"), pass the mutable values to the lambda so that their current values become part of the lambda's state (or create an outer lambda which does this).

--
May 18, 2021
https://issues.dlang.org/show_bug.cgi?id=21929

Vladimir Panteleev <dlang-bugzilla@thecybershadow.net> changed:

           What    |Removed                     |Added
----------------------------------------------------------------------------
             Status|RESOLVED                    |REOPENED
         Resolution|INVALID                     |---

--- Comment #2 from Vladimir Panteleev <dlang-bugzilla@thecybershadow.net> ---
(In reply to deadalnix from comment #0)
> Failure to do allows for very strange things to happen, such as observing mutation in immutable variables.

This however is a good reason to possibly deprecate/disallow doing so at least in @safe code.

--
May 18, 2021
https://issues.dlang.org/show_bug.cgi?id=21929

--- Comment #3 from deadalnix <deadalnix@gmail.com> ---
(In reply to Vladimir Panteleev from comment #1)
> In JavaScript:
> 
> [...]
> 
> So, I don't think D's behavior is unexpected compared to other languages.
> 
> Note that if you change "var" to "let" in JS then it behaves closer as to what you may expect.
> 
> At this point this is part of the language and is not a bug, so I don't think the bugtracker is the proper place to post this.
> 
> The workaround in D is the same as in JavaScript (before "let"), pass the mutable values to the lambda so that their current values become part of the lambda's state (or create an outer lambda which does this).

This code is erroneous. In JS, var declare a variable at the function scope level.

Try again declaring the variable using `let`, which creates a properly scoped variable in JS and see how it goes.

--
May 18, 2021
https://issues.dlang.org/show_bug.cgi?id=21929

Ketmar Dark <ketmar@ketmar.no-ip.org> changed:

           What    |Removed                     |Added
----------------------------------------------------------------------------
                 CC|                            |ketmar@ketmar.no-ip.org

--
May 19, 2021
https://issues.dlang.org/show_bug.cgi?id=21929

Walter Bright <bugzilla@digitalmars.com> changed:

           What    |Removed                     |Added
----------------------------------------------------------------------------
                 CC|                            |bugzilla@digitalmars.com

--- Comment #4 from Walter Bright <bugzilla@digitalmars.com> ---
Let's rewrite it to something that does not use closures:

 int test() @safe {
    int j;
    int*[20] ps;

    for (int i = 0; i < 10; i++) {
        ps[j++] = &i;
    }

    for (int i = 0; i < 10; i++) {
        int index = i;
        ps[j++] = &index;
    }

    int x;
    foreach (p; ps)  {
        x += *p;
    }

    return x;
 }

This code is equivalent in terms of what is happening with references and scopes.

Compiling it with -dip1000 yields:

  Error: address of variable i assigned to ps with longer lifetime
  Error: address of variable index assigned to ps with longer lifetime

Which is pragmatically what the behavior of the delegate example would be, because the delegate is also storing a pointer to the variable.

--
May 19, 2021
https://issues.dlang.org/show_bug.cgi?id=21929

Walter Bright <bugzilla@digitalmars.com> changed:

           What    |Removed                     |Added
----------------------------------------------------------------------------
           Keywords|                            |safe

--
May 19, 2021
https://issues.dlang.org/show_bug.cgi?id=21929

--- Comment #5 from Walter Bright <bugzilla@digitalmars.com> ---
The general rule for determining "what should happen here" when there are abstractions around pointers (such as arrays, delegates, refs, outs, class references, etc.), is to rewrite it in explicit terms of those pointers. The (sometimes baffling) behavior is then exposed for what it actually is, and the behavior should match.

Granted, there is a special kludge in the compiler to sometimes put the variables referenced by the delegate into a closure allocated by the gc, but that doesn't account for variables that go out of scope before the function scope ends. There is no process to make a closure for them, and adding such a capability is likely much more complication than added value, and so should just be an error.

--
May 19, 2021
https://issues.dlang.org/show_bug.cgi?id=21929

--- Comment #6 from deadalnix <deadalnix@gmail.com> ---
(In reply to Walter Bright from comment #4)
> Let's rewrite it to something that does not use closures:
> 
>  int test() @safe {
>     int j;
>     int*[20] ps;
> 
>     for (int i = 0; i < 10; i++) {
>         ps[j++] = &i;
>     }
> 
>     for (int i = 0; i < 10; i++) {
>         int index = i;
>         ps[j++] = &index;
>     }
> 
>     int x;
>     foreach (p; ps)  {
>         x += *p;
>     }
> 
>     return x;
>  }
> 
> This code is equivalent in terms of what is happening with references and scopes.
> 
> Compiling it with -dip1000 yields:
> 
>   Error: address of variable i assigned to ps with longer lifetime
>   Error: address of variable index assigned to ps with longer lifetime
> 
> Which is pragmatically what the behavior of the delegate example would be, because the delegate is also storing a pointer to the variable.

Except it is not. In D, closures do allocate on heap.

--
May 20, 2021
https://issues.dlang.org/show_bug.cgi?id=21929

a11e99z <black80@bk.ru> changed:

           What    |Removed                     |Added
----------------------------------------------------------------------------
                 CC|                            |black80@bk.ru

--- Comment #7 from a11e99z <black80@bk.ru> ---
agree with bug.

man creating new delegate/lambda with new internal state.
what is reason to store just one shared state for created multiple delegetes in
loop? what is using case for it?

if D-team don't want to break current behavior let add new keyword "lambda" that is 100% compatible with "delegate" but it grabs totally new state when created - just changed compile time generated code.

C++ byRef & byValue capturing is more viable.

--
May 20, 2021
https://issues.dlang.org/show_bug.cgi?id=21929

--- Comment #8 from deadalnix <deadalnix@gmail.com> ---
(In reply to a11e99z from comment #7)
> C++ byRef & byValue capturing is more viable.

You'll not that C++'s std::function will allocate on heap if you capture. The equivalent code in C++ WILL allocate in a loop too.

--
« First   ‹ Prev
1 2