Closures are in ways a control flow device, but because they desugar to regular objects, they cannot do things the other control flow primitives can. Both branches of an if-else can mutate the same data, but you can not do the same with a function that takes closures for the branches, because each closure holds references to its captures for its entire lifetime.
So, it would be useful to have a way to create closures that do not hold borrows. Specifically, closures that
- Cannot be given references to their mutable captures as input.
- Do not let any references to their mutable captures escape (be returned/some other way if it exists).
Then any number of these can safely exist and have captured the same data, since it isn't possible for their borrows, which only exist during calls, to overlap. (As long as they aren't Send) This lets functions like the if-else example above work.
Every use of this kind of closure would be checked to ensure that its captures can be legally borrowed at that point, where a use is either a direct call or passing the closure to a function.
Async blocks are also kind of control flow devices, but I'm not sure if something similar could be applied directly to them. These closures could be captured by async blocks, though, and allow cell-free concurrent mutation:
let mut state = State::new();
let mut f = || {...}; // mutate state
let mut g = || {...}; // mutate state
join(
async {...}, // use f
async {...} // use g
).await;
Questions
Are there safety holes I'm missing?
The borrow checker will prevent illegally passing regular references to sealed closures, but how do you detect passing other sealed closures cleanly? Checking how the lifetimes of the arguments match the other closures it might alias with would catch a lot, but I'm not sure how you could improve on that.
Can this behavior be inferred, or should it use a modifier like move, and leave regular closures unchanged?