October 29, 2013
On 10/29/13, 22:37, Ali Çehreli wrote:
> To add to that, Maxim Fomin notes in the D.learn forum that there are
> the following conflicting requirements:

So a copy should have been made of the live object, not the destructed one. Correct?

Sounds like the destruction and the copy are happening in the wrong order.
October 29, 2013
On Tuesday, 29 October 2013 at 21:37:43 UTC, Ali Çehreli wrote:
> To add to that, Maxim Fomin notes in the D.learn forum that there are the following conflicting requirements:
>
> 1) need to allocate struct into heap due to lambda
>
> 2) need to put dtor invocation in the end as usual.
>
> The first one is supported by the closure spec. The second one is the well-know struct destruction upon leaving a scope.

Closures follow the infinite lifetime model – the struct scope is never left, if you want.

This is not exactly a new scenario, destructors on new'd structs aren't called either (unless you manually destroy them).

David
October 29, 2013
On Tuesday, 29 October 2013 at 21:41:25 UTC, Lionello Lunesu wrote:
> So a copy should have been made of the live object, not the destructed one. Correct?

No. There should only be one struct instance, the one living (forever) in the closure context.

David
October 29, 2013
On 10/29/2013 02:41 PM, David Nadlinger wrote:

> Closures follow the infinite lifetime model – the struct scope is never
> left, if you want.

Agreed but the implicit nature of things is troubling. Imagine that the program depends on the destructor to be called:

void foo()
{
    auto s = S(1);
    // ...
}

Later, somebody creates a lambda that silently extends the lifetime of the stack frame (apparently, infinitely). That may be very surprising.

Aside: Is it still a closure if the lambda does not refer to the stack frame? If so, it is even worse:

import std.stdio;

struct S
{
    int i;
    ~this() { writeln("dtor"); }
}

void bar()
{}

auto foo()
{
    S s = S(1);
    return &bar;    // <-- all is well, this is not a closure
}

void main()
{
    foo()();
}

Imagine someone decides to return a lambda from foo() instead:

auto foo()
{
    S s = S(1);
    return {};    // <-- Should 's' be immortal now?
}

Too subtle for my taste! :)

> This is not exactly a new scenario, destructors on new'd structs aren't
> called either (unless you manually destroy them).

At least in the case of a new'ed structs I have a convenient way to destroy them through a pointer. I think in the case of a closure I must go through hoops to access or save that pointer.

Ali

October 29, 2013
On Tuesday, 29 October 2013 at 21:57:25 UTC, Ali Çehreli wrote:
> On 10/29/2013 02:41 PM, David Nadlinger wrote:
>
> > Closures follow the infinite lifetime model – the struct
> scope is never
> > left, if you want.
>
> Agreed but the implicit nature of things is troubling. Imagine that the program depends on the destructor to be called:
>

It may be confusing but I agree that disabling dtor invocation at the end of function scope is a right decision. Technically scope is left, but lifetime continues.

>
> > This is not exactly a new scenario, destructors on new'd
> structs aren't
> > called either (unless you manually destroy them).
>
> At least in the case of a new'ed structs I have a convenient way to destroy them through a pointer. I think in the case of a closure I must go through hoops to access or save that pointer.
>
> Ali

This is not a big deal.

Another but similar case:

import std.stdio;

struct S
{
	int i;
	~this() { writefln("dtor %X, %X", &this, i); }
}

auto foo()
{
	S s = S(1);
	struct SS
	{
		void bar() { s = S(2); }
	}
	return SS();
}

void main()
{
	foo().bar();
	
}

So, one need also to be aware of nested structs (or functions) touching local objects.
October 30, 2013
On 2013-10-29 22:57, Ali Çehreli wrote:

> Imagine someone decides to return a lambda from foo() instead:
>
> auto foo()
> {
>      S s = S(1);
>      return {};    // <-- Should 's' be immortal now?
> }
>
> Too subtle for my taste! :)

Of course not. "s" is never referred to in the returned delegate.

-- 
/Jacob Carlborg
October 30, 2013
On 10/29/13, 22:42, David Nadlinger wrote:
> On Tuesday, 29 October 2013 at 21:41:25 UTC, Lionello Lunesu wrote:
>> So a copy should have been made of the live object, not the destructed
>> one. Correct?
>
> No. There should only be one struct instance, the one living (forever)
> in the closure context.
>
> David

Why? It's a struct. It should be completely fine to create a copy [on the heap] for the closure context.

The way I see closure is that any referenced locals become hidden arguments. Imagine an implicit ", S s" in the closure's parameter list. Everybody would expect 's' (being a value type) to be a copy.

If you want to ensure there's only one instance, you either need to use class instead of struct, or allocate 's' on the heap and use a pointer to S.

L.
October 30, 2013
On Wednesday, 30 October 2013 at 09:20:40 UTC, Lionello Lunesu wrote:
> Why? It's a struct. It should be completely fine to create a copy [on the heap] for the closure context

That's definitely not how D closures work, they always refer to local variables "by reference".

One other place where this tends to crop is for code involving loop variables, but while the behavior might be unexpected to some, discussion has made clear that the code works as intended:

---
void main() {
  import std.stdio;

  void delegate()[] dgs;
  foreach (i; 0 .. 5) dgs ~= { writeln(i); };

  foreach (dg; dgs) dg();
}
---

If structs behaved like you want them to, the snippet would (have to) print 0, 1, 2, 3, 4 as well, and tht's definitely too big a language change to consider at this stage.

David
October 30, 2013
On 10/30/13, 10:56, David Nadlinger wrote:
> ---
> void main() {
>    import std.stdio;
>
>    void delegate()[] dgs;
>    foreach (i; 0 .. 5) dgs ~= { writeln(i); };
>
>    foreach (dg; dgs) dg();
> }
> ---
>
> If structs behaved like you want them to, the snippet would (have to)
> print 0, 1, 2, 3, 4 as well,

Did NOT know that. Thanks for letting me know :)

> and tht's definitely too big a language change to consider at this stage.

Well, that's a separate issue, whether to change the language or not. It's still valuable to discuss unexpected behavior when one runs into it.

L.
October 30, 2013
On Wednesday, 30 October 2013 at 09:56:08 UTC, David Nadlinger wrote:

> One other place where this tends to crop is for code involving loop variables, but while the behavior might be unexpected to some, discussion has made clear that the code works as intended:
>
> ---
> void main() {
>   import std.stdio;
>
>   void delegate()[] dgs;
>   foreach (i; 0 .. 5) dgs ~= { writeln(i); };
>
>   foreach (dg; dgs) dg();
> }
> ---
>
> If structs behaved like you want them to, the snippet would (have to) print 0, 1, 2, 3, 4 as well, and tht's definitely too big a language change to consider at this stage.
>
> David

So D managed to mess up closures, too. And that's after years of countless complaints about the same issue in JS!