Idea for avoiding GC for lambdas
Steven Schveighoffer
schveiguy at gmail.com
Mon Jun 21 20:01:27 UTC 2021
I have some code in my codebase that does stuff like this:
```d
struct S
{
int id;
int field1;
}
auto foo(S[] arr, int someval) // @nogc not allowed :(
{
// remove all the elements of arr that have someval for field1
import std.algorithm;
return arr.filter!(v => v.field1 == someval);
}
```
While this works, and is nice, it's using a lambda with local references
to filter data. This means a GC closure allocation is involved.
However, the value that is referenced (someval) is the only thing
needed, and it seems a huge waste to allocate a stack frame *just* for that.
Let's look at another way to do this. `filter` does not have an overload
which accepts a *value* to compare to, but `find` does. So let's build a
new `filter` that uses it:
```d
auto nogcfilter(alias pred, T, V)(T[] rng, V val)
{
import std.range;
import std.algorithm : find;
static struct Filter {
T[] src;
V val;
auto front() { return src.front; }
void popFront() {src.popFront; src = src.find!pred(val); }
auto empty() { return src.empty; }
}
return Filter(rng, val);
}
auto foo(S[] arr, int someval) @nogc // :)
{
// note: the explicit lambda types are needed for some reason
return arr.nogcfilter!((S v, int a) => v.field1 == a)(someval);
}
```
Now we get filter capability, but without the GC.
This works great because the context items used do NOT require any
mutability. It also works great because the filter function is building
a custom type ANYWAY.
Now, we could add this kind of functionality to `filter`, but being the
lazy programmer that I am, what if the compiler could do this for me? In
most cases, I don't want to sleuth out what context is needed (which
might change as the code evolves). To force the user to both invent the
context type (here, it's just an `int`, but it may require other values)
and explicitly pass it is a bit verbose (I already don't like having to
pass the `int`, and declaring the parameter).
What if, a function that accepts a lambda, could also accept a *context*
which the compiler made up and passed in? Here's how it would possibly
look (with a new magic type `__context`):
```d
auto nogcfilter(alias pred, T, __context...)(T[] rng, __context ctxt)
{
import std.range;
import std.algorithm : find;
static struct Filter {
T[] src;
__context val;
auto front() { return src.front; }
void popFront() {src.popFront; src = src.find!pred(val); }
auto empty() { return src.empty; }
}
return Filter(rng, ctxt);
}
auto foo(S[] arr, int someval) @nogc
{
return arr.nogcfilter!((v) => v.field1 == someval);
}
```
The compiler would see there's a lambda involved, automatically pass the
context parameter `someval` into the function itself, and everything
magically works. It would only be used when a closure would otherwise be
allocated, and the function being called declares the context parameter.
Disclaimer: I am not a language developer.
Does this make sense? Is there anything feasible that might be similar?
Would it be more feasible with different syntax?
I realize there are implications if the context parameter is mutable, so
perhaps we would have to require only const contexts (though the
compiler might be able to figure out that something isn't modified
anywhere, and allow it to go in).
-Steve
More information about the Digitalmars-d
mailing list