digitalmars.D.learn - Nested delegates and closure allocations
- Anonymouse (25/31) Jan 16 I'm increasingly using nested delegates to partition code.
- FeepingCreature (2/25) Jan 16 Yes.
- Anonymouse (44/47) Jan 16 Thank you.
- FeepingCreature (11/29) Jan 16 Correct. The problem is that `receiveTimeout` is defined as a
- Anonymouse (3/4) Jan 17 Thanks, I think I understand.
I'm increasingly using nested delegates to partition code. ```d void foo(Thing thing) { void sendThing(const string where, int i) { send(thing, where, i); } sendThing("bar", 42); } ``` ...where the nested `sendThing` sometimes returns something, sometimes doesn't. `Thing` may be a class or a value type, `thing` may be a parameter to the parent function, may be a variable previously declared in the parent function, may be mutable or immutable, may be modified inside `sendThing`; any combination of things. If `sendThing` doesn't need to access the scope of `foo` I mark it `static` to enforce that, but mostly it does. From the spec:3. Those referenced stack variables that make up the closure are allocated on the GC heap, unless: * The closure is passed to a scope parameter. * The closure is an initializer for a scope variable. * The closure is assigned to a scope variable.I'm generally not storing the delegates or passing them around as values, so I don't think the thing about scope variables and parameters *directly* applies. Am I safe as long as I don't do something like, pass `&sendThing` as an argument to `std.concurrency.receive`?
Jan 16
On Tuesday, 16 January 2024 at 10:56:58 UTC, Anonymouse wrote:I'm increasingly using nested delegates to partition code. ```d void foo(Thing thing) { void sendThing(const string where, int i) { send(thing, where, i); } sendThing("bar", 42); } ``` ...Yes.3. Those referenced stack variables that make up the closure are allocated on the GC heap, unless: * The closure is passed to a scope parameter. * The closure is an initializer for a scope variable. * The closure is assigned to a scope variable.I'm generally not storing the delegates or passing them around as values, so I don't think the thing about scope variables and parameters *directly* applies. Am I safe as long as I don't do something like, pass `&sendThing` as an argument to `std.concurrency.receive`?
Jan 16
On Tuesday, 16 January 2024 at 13:45:22 UTC, FeepingCreature wrote:Thank you. And to make sure I don't misunderstand the spec; in the case I *do* have a delegate I want to pass elsewhere, and `scope dg = &myFun;` *does* compile, passing that `dg` around won't allocate a closure? ```d void foo(Thing thing) nogc { void sendThing(const string where, int i) { send(thing, where, i); } receiveTimeout(Duration.zero, &sendThing); } ``` The above naturally won't compile because `std.concurrency.receiveTimeout` requires the garbage collector, but notably in the error message, this is included; ``` onlineapp.d(10): Error: function `onlineapp.foo` is ` nogc` yet allocates closure for `foo()` with the GC onlineapp.d(12): `onlineapp.foo.sendThing` closes over variable `thing` at onlineapp.d(10) ``` If I make a `scope` variable of the delegate and pass *it* to `receiveTimeout`, there no longer seems to be any mention of the closure in the error (given 2.092 or later). ```d void foo(Thing thing) nogc { void sendThing(const string where, int i) { send(thing, where, i); } scope scopeSendThing = &sendThing; receiveTimeout(Duration.zero, scopeSendThing); } ``` Ignoring that it doesn't compile for other reasons; provided `scope scopeSendThing = &sendThing;` compiles -- as in, `&sendThing` is eligible for `scope` -- is this a valid workaround?Am I safe as long as I don't do something like, pass `&sendThing` as an argument to `std.concurrency.receive`?Yes.
Jan 16
On Tuesday, 16 January 2024 at 15:39:07 UTC, Anonymouse wrote:If I make a `scope` variable of the delegate and pass *it* to `receiveTimeout`, there no longer seems to be any mention of the closure in the error (given 2.092 or later). ```d void foo(Thing thing) nogc { void sendThing(const string where, int i) { send(thing, where, i); } scope scopeSendThing = &sendThing; receiveTimeout(Duration.zero, scopeSendThing); } ``` Ignoring that it doesn't compile for other reasons; provided `scope scopeSendThing = &sendThing;` compiles -- as in, `&sendThing` is eligible for `scope` -- is this a valid workaround?Correct. The problem is that `receiveTimeout` is defined as a template variadic function: it *can* take a scoped function, but it doesn't (can't) declare that its argument is always scoped, so since scoped parameters are opt-in, it defaults to unscoped. And &sendThing has to also default to unscoped, because you can pass unscoped values to scoped parameters but not the other way around, so it defaults to the most generic type available. With your scoped variable you provide DMD the critical hint that actually you want the closure to be scoped, and once the value is scoped it stays scoped.
Jan 16
On Tuesday, 16 January 2024 at 17:21:12 UTC, FeepingCreature wrote:Correct. [...]Thanks, I think I understand.
Jan 17