Thanks, this makes a lot more sense now.
I also realized after reading #15313 that async is intentionally much weaker semantically than I initially assumed. In particular, I had missed that execution itself is not guaranteed until await /cancel , and that eager execution combined with a full completion queue could potentially block as well.
So for actual fire-and-forget style execution, concurrent definitely seems like the more appropriate primitive.
Also good point regarding pinning / self-referential storage. The original init(self: *Self) form is technically fine if the object never moves after initialization, but it would probably be too easy to accidentally shoot myself in the foot by returning or copying the struct later.
For example:
fn makeReaper(io: std.Io) DetachedTaskReaper(10) {
var r: DetachedTaskReaper(10) = undefined;
r.init(io);
return r;
}
Heap allocation/pinning is likely the safer API shape there.