www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Error: variable 'xyz' has scoped destruction, cannot build closure

reply Jon Degenhardt <jond noreply.com> writes:
I got the compilation error in the subject line when trying to 
create a range via std.range.generate. Turns out this was caused 
by trying to create a closure for 'generate' where the closure 
was accessing a struct containing a destructor.

The fix was easy enough: write out the loop by hand rather than 
using 'generate' with a closure. What I'm wondering/asking is if 
there alternate way to do this that would enable the 'generate' 
approach. This is more curiosity/learning at this point.

Below is a stripped down version of what I was doing. I have a 
struct for output buffering. The destructor writes any data left 
in the buffer to the output stream. This gets passed to routines 
performing output. It was this context that I created a generator 
that wrote to it.

----example.d-----
struct BufferedStdout
{
     import std.array : appender;

     private auto _outputBuffer = appender!(char[]);

     ~this()
     {
         import std.stdio : write;
         write(_outputBuffer.data);
         _outputBuffer.clear;
     }

     void appendln(T)(T stuff)
     {
         import std.range : put;
         put(_outputBuffer, stuff);
         put(_outputBuffer, "\n");
     }
}

void foo(BufferedStdout output)
{
     import std.algorithm : each;
     import std.conv : to;
     import std.range: generate, takeExactly;
     import std.random: Random, uniform, unpredictableSeed;

     auto randomGenerator = Random(unpredictableSeed);
     auto randomNumbers = generate!(() => uniform(0, 1000, 
randomGenerator));
     auto tenRandomNumbers = randomNumbers.takeExactly(10);
     tenRandomNumbers.each!(n => output.appendln(n.to!string));
}

void main(string[] args)
{
     foo(BufferedStdout());
}
----End of  example.d-----

Compiling the above results in:

    $ dmd example.d
    example.d(22): Error: variable `example.foo.output` has scoped 
destruction, cannot build closure

As mentioned, using a loop rather than 'generate' works fine, but 
help with alternatives that would use generate would be 
appreciated.

The actual buffered output struct has more behind it than shown 
above, but not too much. For anyone interested it's here:  
https://github.com/eBay/tsv-utils/blob/master/common/src/tsvutil.d#L358
Oct 04 2018
parent reply Nicholas Wilson <iamthewilsonator hotmail.com> writes:
On Friday, 5 October 2018 at 03:27:17 UTC, Jon Degenhardt wrote:
 I got the compilation error in the subject line when trying to 
 create a range via std.range.generate. Turns out this was 
 caused by trying to create a closure for 'generate' where the 
 closure was accessing a struct containing a destructor.

 The fix was easy enough: write out the loop by hand rather than 
 using 'generate' with a closure. What I'm wondering/asking is 
 if there alternate way to do this that would enable the 
 'generate' approach. This is more curiosity/learning at this 
 point.

 Below is a stripped down version of what I was doing. I have a 
 struct for output buffering. The destructor writes any data 
 left in the buffer to the output stream. This gets passed to 
 routines performing output. It was this context that I created 
 a generator that wrote to it.

 ----example.d-----
 struct BufferedStdout
 {
     import std.array : appender;

     private auto _outputBuffer = appender!(char[]);

     ~this()
     {
         import std.stdio : write;
         write(_outputBuffer.data);
         _outputBuffer.clear;
     }

     void appendln(T)(T stuff)
     {
         import std.range : put;
         put(_outputBuffer, stuff);
         put(_outputBuffer, "\n");
     }
 }

 void foo(BufferedStdout output)
 {
     import std.algorithm : each;
     import std.conv : to;
     import std.range: generate, takeExactly;
     import std.random: Random, uniform, unpredictableSeed;

     auto randomGenerator = Random(unpredictableSeed);
     auto randomNumbers = generate!(() => uniform(0, 1000, 
 randomGenerator));
     auto tenRandomNumbers = randomNumbers.takeExactly(10);
     tenRandomNumbers.each!(n => output.appendln(n.to!string));
 }

 void main(string[] args)
 {
     foo(BufferedStdout());
 }
 ----End of  example.d-----

 Compiling the above results in:

    $ dmd example.d
    example.d(22): Error: variable `example.foo.output` has 
 scoped destruction, cannot build closure

 As mentioned, using a loop rather than 'generate' works fine, 
 but help with alternatives that would use generate would be 
 appreciated.

 The actual buffered output struct has more behind it than shown 
 above, but not too much. For anyone interested it's here:  
 https://github.com/eBay/tsv-utils/blob/master/common/src/tsvutil.d#L358
tenRandomNumbers.each!((n,o) => o.appendln(n.to!string))(output); or tenRandomNumbers.each!((n, ref o) => o.appendln(n.to!string))(output); should hopefully do the trick (run.dlang.io seems to be down atm). The problem is that `output` is captured by the delegate and this somehow causes problems (idk what or why). If the (perviously captured) variables are instead passed as parameters then there is no need to create a closure for the variable, so no problem. This is also the way to ensure that lambdas are nogc if you ever need that.
Oct 04 2018
parent reply Nicholas Wilson <iamthewilsonator hotmail.com> writes:
On Friday, 5 October 2018 at 06:22:57 UTC, Nicholas Wilson wrote:
 tenRandomNumbers.each!((n,o) => 
 o.appendln(n.to!string))(output);

 or

 tenRandomNumbers.each!((n, ref o) => 
 o.appendln(n.to!string))(output);

 should hopefully do the trick (run.dlang.io seems to be down 
 atm).
Alas is does not because each does not accept additional argument other than the range. Shouldn't be hard to fix though.
Oct 04 2018
next sibling parent Jon Degenhardt <jond noreply.com> writes:
On Friday, 5 October 2018 at 06:44:08 UTC, Nicholas Wilson wrote:
 On Friday, 5 October 2018 at 06:22:57 UTC, Nicholas Wilson 
 wrote:
 tenRandomNumbers.each!((n,o) => 
 o.appendln(n.to!string))(output);

 or

 tenRandomNumbers.each!((n, ref o) => 
 o.appendln(n.to!string))(output);

 should hopefully do the trick (run.dlang.io seems to be down 
 atm).
Alas is does not because each does not accept additional argument other than the range. Shouldn't be hard to fix though.
Yeah, that's what I was seeing also. Thanks for taking a look. Is there perhaps a way to limit the scope of the delegate to the local function? Something that would tell the compiler the delegate has a lifetime shorter than the struct. One specific it points out is that this a place where the BufferedOutputRange I wrote cannot be used interchangeably with other output ranges. It's minor, but the intent was to be able to pass this anyplace an output range could be used.
Oct 04 2018
prev sibling parent reply Nicholas Wilson <iamthewilsonator hotmail.com> writes:
On Friday, 5 October 2018 at 06:44:08 UTC, Nicholas Wilson wrote:
 Alas is does not because each does not accept additional 
 argument other than the range. Shouldn't be hard to fix though.
https://issues.dlang.org/show_bug.cgi?id=19287
Oct 04 2018
parent reply Paul Backus <snarwin gmail.com> writes:
On Friday, 5 October 2018 at 06:56:49 UTC, Nicholas Wilson wrote:
 On Friday, 5 October 2018 at 06:44:08 UTC, Nicholas Wilson 
 wrote:
 Alas is does not because each does not accept additional 
 argument other than the range. Shouldn't be hard to fix though.
https://issues.dlang.org/show_bug.cgi?id=19287
You can thread multiple arguments through to `each` using `std.range.zip`: tenRandomNumbers .zip(repeat(output)) .each!(unpack!((n, output) => output.appendln(n.to!string))); Full code: https://run.dlang.io/is/Qe7uHt
Oct 05 2018
parent reply Jon Degenhardt <jond noreply.com> writes:
On Friday, 5 October 2018 at 16:34:32 UTC, Paul Backus wrote:
 On Friday, 5 October 2018 at 06:56:49 UTC, Nicholas Wilson 
 wrote:
 On Friday, 5 October 2018 at 06:44:08 UTC, Nicholas Wilson 
 wrote:
 Alas is does not because each does not accept additional 
 argument other than the range. Shouldn't be hard to fix 
 though.
https://issues.dlang.org/show_bug.cgi?id=19287
You can thread multiple arguments through to `each` using `std.range.zip`: tenRandomNumbers .zip(repeat(output)) .each!(unpack!((n, output) => output.appendln(n.to!string))); Full code: https://run.dlang.io/is/Qe7uHt
Very interesting, thanks. It's a clever way to avoid the delegate capture issue. (Aside: A nested function that accesses 'output' from lexical context has the same issue as delegates wrt to capturing the variable.)
Oct 05 2018
parent Nicholas Wilson <iamthewilsonator hotmail.com> writes:
On Friday, 5 October 2018 at 19:31:56 UTC, Jon Degenhardt wrote:
 On Friday, 5 October 2018 at 16:34:32 UTC, Paul Backus wrote:
 You can thread multiple arguments through to `each` using 
 `std.range.zip`:

     tenRandomNumbers
         .zip(repeat(output))
         .each!(unpack!((n, output) => 
 output.appendln(n.to!string)));

 Full code: https://run.dlang.io/is/Qe7uHt
Very interesting, thanks. It's a clever way to avoid the delegate capture issue. (Aside: A nested function that accesses 'output' from lexical context has the same issue as delegates wrt to capturing the variable.)
Note that this solution may do a lot of output and hence running of the destructor. Use: `.zip(repeat(&output))` to avoid that.
Oct 05 2018