The following range Foo is trying to be helpful by adding as many attributes as it can ('const' is missing because ranges cannot be 'const' because at least popFront() needs to be mutable):

import std.algorithm;

struct Foo(R) {
    R r;
    int i;

    bool empty() @nogc nothrow pure @safe scope {
        return r.empty;
    }

    auto front() @nogc nothrow pure @safe scope {
        return r.front;
    }

    auto popFront() @nogc nothrow pure @safe scope {
        r.popFront();
    }
}

auto foo(R)(R r) {
    return Foo!R(r);
}

int count;

void main() {
    [ 1, 2 ]
        .map!((i) {
                ++count;    // <-- Impure
                return i;
            })
        .foo;
}

Of course there are compilation errors inside the member functions because e.g. r.front that it dispatches to is not pure (it touches the module variable 'count'):

Error: `pure` function `deneme.Foo!(MapResult!(__lambda1, int[])).Foo.front` cannot call impure function `deneme.main.MapResult!(__lambda1, int[]).MapResult.front`

(Other attributes would cause similar issues if e.g. the lambda were @nogc.)

What are best practices here?

Is this accurate: Because Foo is a template, it should not put any attribute on member functions? Or only member functions that use a member that depends on a template parameter? And non-members that are templates?

It is scary because Foo works just fine until it is used with impure code.

Is putting function attributes on unittest blocks for catching such issues?

@nogc nothrow pure @safe
unittest
{
    // ...
}

No, it isn't because unless my unittest code is impure, I can't catch my incorrect 'pure' etc. on my member functions.

Help! :)

Ali

Reply via email to