argparse icon indicating copy to clipboard operation
argparse copied to clipboard

One does not simply pass a closure to a `LazyString`

Open SirNickolas opened this issue 7 months ago • 1 comments

Discovered this when I was trying to deduplicate Complete.InitCmd. A reduced example that should be easier to follow:

struct Description {
    string delegate() dg;
}

auto createCompleter(string desc) {
    @Description(() => desc) // Seems good so far.
    struct Complete { }

    return Complete.init; // Err... We haven't stored `desc` anywhere, have we?
}

void main() {
    import std.stdio: writeln;
    import std.traits: getUDAs;

    enum completer = createCompleter("Description.");
    writeln(getUDAs!(typeof(completer), Description)[0].dg()); // Here.
}

The compiler blames: delegate test.createCompleter.__lambda3 is a nested function and cannot be accessed from D main. And it is right. Therefore, a delegate can be used with a LazyString only if it does not escape its declaration scope.

UDAs are attached at struct-declaration time, but variables we want to close over are only available at instance-creation time. Unfortunately, I can’t think of a good solution here. One idea comes to my mind: add a third option to LazyString, which should be a struct with no members, to signify even more runtime strings. Then a caller can test for this and invoke cmd.getDescription. That should work in principle, but it requires patching every callsite to account for that, which I’m not happy with. Any better ideas?

SirNickolas avatar Nov 23 '23 05:11 SirNickolas