Attribute transference from callbacks?
Zach Tollen
notmyrealemail at whatever.eee
Sun Dec 15 06:47:31 UTC 2024
On Wednesday, 11 December 2024 at 22:36:27 UTC, Manu wrote:
> On Wed, 11 Dec 2024 at 08:41, Zach Tollen via Digitalmars-d <
>> This was brought up in a [thread][thread1] in DIP Ideas. I
>> contributed my idiosyncratic thoughts here:
>>
>> https://forum.dlang.org/post/rtvjsdyuqwmzwiggsolw@forum.dlang.org
>>
>> Suggestion #2 in that post addresses the issue you raise. In
>> short, I proposed that the language default to inheriting
>> attributes for the enclosing function at the call site, and
>> then suggested a (callable function/delegate) parameter
>> keyword `@noimply` for those rare cases when you *don't* want
>> the function to inherit the characteristics of the delegate
>> that you pass. I tried to explain it in the post.
>
> This appears to suffer from the same category of problem as
> Schveighoffer's suggestion; it's that you can't reason about
> the function statically anymore.
I'm pretty sure you can. I think there's no avoiding the fact
that the the call site is only place where the proper behavior
for these calls can be determined. The best the function
definition can do is to let the caller know (via e.g.,
`@noimply`) whether the function is able to *negate* any
`@system`/`throw`/@gc/impure effects (the latter two should be
understood with their obvious meaning).
For example, a function could negate these effects by not
actually *calling* the delegate. Or by other means, which vary
depending on the attribute. But since negation is the rare case,
by default the language should *assume*, I believe, that any
delegate/function that is passed in as an argument *will* be
called. Which is to say, all the inout(...) spam in your
suggestion would work — but it shouldn't need to: it should work
*implicitly*. It makes sense: If you pass in a delegate which is
`@system`/`throw`/@gc/impure, the call should be treated as a
`@system`/`throw`/@gc/impure action, unless specifically negated.
> Again, the reason I approach it from the perspective of
> inout(...) is that you can always statically reason about it,
> and its guarantees are appropriately applied at compile time.
My suggestion applies all the same guarantees at compile time.
The negating effects of `@noimply` can be statically checked. For
example:
```d
void f(void delegate() sink) {
sink();
}
void g() nothrow {
f(() { throw new Exception(""); } ); // Error: function f()
called with delegate which has been inferred `throw`, in
`nothrow` function g()
}
```
Let's tell it not to imply that the sink throws/doesn't throw:
```d
void h(void delegate() @noimply(throw) sink) {
// The compiler tests the delegate by assuming sink() is
@system/throw/@gc/impure
sink(); // Error: delegate sink() is @noimply(throw), but may
throw
}
```
The error above is the result of checking the delegate's
attributes separately from the attributes for `h()`. This would
happen for every use of a parameter delegate tagged with
`@noimply`. (For parameters *not* tagged with `@noimply`, no
checking is necessary — the call site propagates all the
`@system`/`throw`/@gc/impure characteristics of the delegate that
has been passed.)
In order to get it working, you have to make sure that the throw
can't escape:
```d
void j(void delegate() @noimply(throw) sink) {
// passes, statically checks that the call to sink() takes
place in a context that can't throw
try { sink(); }
catch(Exception) {}
}
void k() nothrow {
// passes(!) because the parameter in j() has been statically
confirmed to not propagate the throw
j(() { throw new Exception(""); } );
}
```
I believe this system has all the benefits of the other
suggestions, but with almost no syntactic noise.
More information about the Digitalmars-d
mailing list