Skip to content

ACP: Opt-in to calling GlobalAlloc in std's internals #743

@Mark-Simulacrum

Description

@Mark-Simulacrum

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    T-libs-apiapi-change-proposalA proposal to add or alter unstable APIs in the standard libraries

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions