Summary
Introduce a notion of execution context to Rust.
Motivation
This is primarily motivated by an issue with the Alloc
trait that has been discussed in Tracking issue for custom allocators in standard collections (and possibly elsewhere, although that’s all I could find).
In the current Alloc
trait, dealloc
is a method. This means that if you want to create a type which takes an A: Alloc
type parameter, and you want to have some of your code call dealloc
, you need to store an instance of A
(or a reference to A
) internally. For the global allocator, A
is a ZST, so this doens’t matter. For non-global allocators, however, A
or a reference to it is usually going to be at least a machine word in size.
For types that store large objects (e.g., Vec
), this isn’t a big deal. For types which store small objects, however, this is a huge deal. Box<T, A: Alloc>
, for example, doubles in size at a minimum from storing a pointer to T
to storing a pointer to T
and a reference to A
. If you’re implementing something like a linked list or a tree, this represents a near-doubling of the size of your data structure.
In my mind, this points to a broader point about Rust: Rust’s story that resource cleanup always happens safely via ownership + drop
implicitly relies on ambient authority. For example, Box<T>
can only dealloc its contents on drop
because the global heap is ambient authority. When resource cleanup needs to rely on non-ambient state, we’re left with no good options. Storing an A: Alloc
is such a case.
Guide-level Explanation
My proposal is to introduce a notion of “execution context” to Rust. It would allow ambient authority to be replaced by context-scoped state. An execution context would be:
- Set by a caller
- In place for a function call (or a scope within a function call) and all of its sub-calls
- Any code could query for the current execution context
- Any code could set a sub-context which would override the existing context for the duration of some smaller scope, similar to variable shadowing
It would be very similar to Go’s context
, Scheme’s parameters, or Haskell’s Reader
monad. In the case of an allocator, if a custom (non-heap) allocator were used (for example by a particular data structure), code (for example, methods on that data structure) would set the allocator as an element of the execution context. drop
implementations - crucially, including Box
- would have access to the allocator in order to perform deallocation without needing to store it inside themselves.
Reference-level Explanation
There is none. I don’t actually know how we’d implement this - I just want to prompt discussion.
Some important questions include:
- Would this need explicit language support?
- How can we ensure that this works in
no_std
?
I’m especially interested to hear feedback on:
- Would this actually solve these sorts of ambient authority problems, or are there holes in this idea that folks notice?
- Are there other good examples (besides custom allocators) of Rust code that would benefit from such a system?