Proposal
Problem statement
Since 1.93 (rust-lang/rust#144465), std has guaranteed that it won't call #[global_allocator] GlobalAlloc from these places (https://doc.rust-lang.org/nightly/std/alloc/trait.GlobalAlloc.html#re-entrance):
For this reason, one should generally stick to library features available through core, and avoid using std in a global allocator. A few features from std are guaranteed to not use #[global_allocator] to allocate:
In some cases, a GlobalAlloc impl doesn't use those APIs (e.g., it also directly calls System in all cases -- perhaps just tracking statistics in a global static atomic or overriding how to call the system allocator), and so this guarantee is not needed and is harmful to the goal of the override. This ACP proposes a way for impls of GlobalAlloc to opt-in to allow std to always call into them.
Motivating examples or use cases
See rust-lang/rust#144465 (comment). I've written the trivial wrapper that just aims for "count currently allocated bytes" in a global static, which is trivially fine to be re-entrant.
Solution sketch
Add a new associated constant to GlobalAlloc:
unsafe trait GlobalAlloc {
/// Does this global allocator use std from within alloc/dealloc/realloc?
///
/// If this is true, then the standard library will call the System
/// allocator instead for allocations from types guaranteed to be re-entrant safe (per the docs above).
fn uses_std(&self) -> bool {
true
}
}
impl GlobalAlloc for System {
// The system allocator's implementation may use the same
// concepts, but shouldn't share state with the application's standard library.
fn uses_std(&self) -> bool {
false
}
}
Within std, where we currently directly call System.alloc/dealloc, we'd instead branch on the return of an extern function (https://github.com/rust-lang/rust/blob/d00ba922591daa3d0a8b0f3cdf54c610097c24ea/library/alloc/src/alloc.rs#L11) that the compiler synthesizes returning the bool from the global_allocator impl.
Alternatives
- Do nothing, accepting that a subset of std-invoked allocations cannot be overriden with
#[global_allocator]. In some cases users can override these by platform-specific strategies (e.g., overriding the malloc symbol directly).
- Function rather than constant. The underlying Rust implementation will need to generate a function or static to inject the symbol cross-crate, but since we require that the return is unchanged over time.
- Different name. I'm not a huge fan of "USES_STD", but it feels like it's a fairly future-proof name. To some extent the property we actually care about is "re-entrant safe", but that feels complicated.
- Wait on
try_as_dyn or similar feature, and make this be a trait ReentrantGlobalAlloc that we downcast in the compiler. In practice, this seems more complicated for std/compiler, and not much better for users. Most GlobalAlloc implementations should override this.
- More granular consts. In principle, if std wants to expand the guarantees of calling System to more places (e.g., Mutex), we might want to condition that on whether it's actually necessary for the particular allocator. So we could have
const USES_STD_THREAD, const USES_STD_MUTEX, etc. I think this is probably not warranted and could be done later (at some potential cost to naming niceness).
Links and related work
cc rust-lang/wg-allocators#25 which has some discussion of re-entrancy. @the8472 I think you mentioned an issue more closely related to this, but I don't see it right now.
cc @orlp @laurmaedje
What happens now?
This issue contains an API change proposal (or ACP) and is part of the libs-api team feature lifecycle. Once this issue is filed, the libs-api team will review open proposals as capability becomes available. Current response times do not have a clear estimate, but may be up to several months.
Possible responses
The libs team may respond in various different ways. First, the team will consider the problem (this doesn't require any concrete solution or alternatives to have been proposed):
- We think this problem seems worth solving, and the standard library might be the right place to solve it.
- We think that this probably doesn't belong in the standard library.
Second, if there's a concrete solution:
- We think this specific solution looks roughly right, approved, you or someone else should implement this. (Further review will still happen on the subsequent implementation PR.)
- We're not sure this is the right solution, and the alternatives or other materials don't give us enough information to be sure about that. Here are some questions we have that aren't answered, or rough ideas about alternatives we'd want to see discussed.
Proposal
Problem statement
Since 1.93 (rust-lang/rust#144465), std has guaranteed that it won't call
#[global_allocator]GlobalAlloc from these places (https://doc.rust-lang.org/nightly/std/alloc/trait.GlobalAlloc.html#re-entrance):In some cases, a GlobalAlloc impl doesn't use those APIs (e.g., it also directly calls System in all cases -- perhaps just tracking statistics in a global static atomic or overriding how to call the system allocator), and so this guarantee is not needed and is harmful to the goal of the override. This ACP proposes a way for impls of GlobalAlloc to opt-in to allow std to always call into them.
Motivating examples or use cases
See rust-lang/rust#144465 (comment). I've written the trivial wrapper that just aims for "count currently allocated bytes" in a global static, which is trivially fine to be re-entrant.
Solution sketch
Add a new associated constant to
GlobalAlloc:Within std, where we currently directly call System.alloc/dealloc, we'd instead branch on the return of an extern function (https://github.com/rust-lang/rust/blob/d00ba922591daa3d0a8b0f3cdf54c610097c24ea/library/alloc/src/alloc.rs#L11) that the compiler synthesizes returning the bool from the global_allocator impl.
Alternatives
#[global_allocator]. In some cases users can override these by platform-specific strategies (e.g., overriding themallocsymbol directly).try_as_dynor similar feature, and make this be atrait ReentrantGlobalAllocthat we downcast in the compiler. In practice, this seems more complicated for std/compiler, and not much better for users. Most GlobalAlloc implementations should override this.const USES_STD_THREAD,const USES_STD_MUTEX, etc. I think this is probably not warranted and could be done later (at some potential cost to naming niceness).Links and related work
cc rust-lang/wg-allocators#25 which has some discussion of re-entrancy. @the8472 I think you mentioned an issue more closely related to this, but I don't see it right now.
cc @orlp @laurmaedje
What happens now?
This issue contains an API change proposal (or ACP) and is part of the libs-api team feature lifecycle. Once this issue is filed, the libs-api team will review open proposals as capability becomes available. Current response times do not have a clear estimate, but may be up to several months.
Possible responses
The libs team may respond in various different ways. First, the team will consider the problem (this doesn't require any concrete solution or alternatives to have been proposed):
Second, if there's a concrete solution: