Search code examples
d

Compile-time fold results in error when called with a lambda


I'm trying to fold an array at compile time, and store the result in an enum. It works just fine when the enum (and call to fold) is at module-level, but compilation fails when its both contained within a struct and called using a lambda.

Here's a simple example of some failing code:

import std.algorithm.iteration;
import std.stdio;

struct Foo
{
    // Version 1 (works)
    //enum x = [ 1, 2, 3 ].fold!"a * b";

    // Version 2 (works)
    //enum x = [ 1, 2, 3 ].fold!mult;

    // Version 3 (broken)
    enum x = [ 1, 2, 3 ].fold!((a, b) => a * b);


    pragma(msg, "x = ", x);
}

// Outside of the struct, it works
enum y = [ 1, 2, 3 ].fold!((a, b) => a * b);
pragma(msg, "y = ", y);

int mult(int a, int b)
{
    return a * b;
}

void main(){}

(Versions 1 and 2, which are commented out, compile just fine. It's just Version 3 that has problems.)

Upon compiling, the following error is thrown:

C:\D\dmd2\windows\bin\..\..\src\phobos\std\algorithm\iteration.d(3690): Error: `this.__lambda2` has no value
C:\D\dmd2\windows\bin\..\..\src\phobos\std\algorithm\iteration.d(3690):        while evaluating: `static assert(((int)).length == fun.length)`
C:\D\dmd2\windows\bin\..\..\src\phobos\std\algorithm\iteration.d(3697): Error: `this.__lambda2` has no value
C:\D\dmd2\windows\bin\..\..\src\phobos\std\algorithm\iteration.d(3718): Error: `this.__lambda2` has no value
C:\D\dmd2\windows\bin\..\..\src\phobos\std\algorithm\iteration.d(3636): Error: template instance `broken.Foo.reduce!((a, b) => a * b).reduceImpl!(false, int[], int)` error instantiating
C:\D\dmd2\windows\bin\..\..\src\phobos\std\algorithm\iteration.d(4086):        instantiated from here: `reduce!(int[])`
.\broken.d(13):        instantiated from here: `fold!(int[])`
x = .\broken.d(13): Error: CTFE failed because of previous errors in `fold`
.\broken.d(16):        while evaluating `pragma(msg, x)`
y = 6
Failed: ["C:\\D\\dmd2\\windows\\bin\\dmd.exe", "-v", "-o-", ".\\broken.d", "-I."]

I've tried looking at the source code mentioned in the error, but the concepts it uses are beyond my current level of knowledge of D.

I initially assumed that lambdas might not work properly at compile-time, but enum y evaluates correctly, so I'm guessing it's not that...

I'm using DMD v2.086.1, but had the same problem using LDC 1.16.0 and 1.14.0.


Solution

  • This is the compiler not being very good at figuring out if a lambda needs access to its context. If you'd written something like this:

    struct S {
        int n;
        int fun() {
            import std.algorithm.iteration : fold;
            return [1,2,3].fold!((a,b) => a*b*n);
        }
    }
    

    It should be clear that the lambda above needs access to n in the struct. In the same way, the compiler errs on the side of caution for enum x = [ 1, 2, 3 ].fold!((a, b) => a * b);, and assumes there's some state inside Foo that will affect the result of the calculation. Filed this as issue 20077.

    You have already found some workarounds, and there's another worth mentioning - adding argument types to the lambda:

    enum x = [ 1, 2, 3 ].fold!((int a, int b) => a * b);
    

    This way, the compiler figures out what info the lambda needs at an earlier point, and is able to figure out that it doesn't need access to the surrounding scope.