rust-lang / wg-allocators

Home of the Allocators working group: Paving a path for a standard set of allocator traits to be used in collections!
http://bit.ly/hello-wg-allocators
205 stars 9 forks source link

Should a global allocator always be required, even if it isn't used? #72

Open lachlansneff opened 3 years ago

lachlansneff commented 3 years ago

The downside of not moving Box<T, A>, Vec<T, A>, and other allocator generic collections into libcore is that to use them, you have to import the alloc crate. This has the side effect of not building unless you specify a #[global_allocator] and an #[alloc_error_handler]. This seems inconvenient. Thoughts?

Amanieu commented 3 years ago

I feel that situations where you would want to use dynamically allocated data structures without a global allocator are sufficiently rare that we can just tell people to use a global allocator that always panics.

Fixing this the "proper" way would require messing with default generic parameters at the language level which is likely to be very messy to implement.

petertodd commented 3 years ago

One scenario where that might be useful would be for crates where a subset of the functionality would be used in embedded contexts. Many data structures are much easier to create with an allocator, but using the data structure without one is perfectly feasible.

I haven't fully fleshed out this design. But for my OpenTimestamps protocol a timestamp proof is basically a tree, and the binary serialization format was designed to be usable in-place in embedded contexts. A copy-on-write API could work kinda like the following:

enum TimestampProof<'a, A = Global> {
    Clean(&'a [u8]),
    Dirty(Box<InMemoryProof<A>, A>),
}

So in an embedded context, you'd set A to be a dummy allocator that can't actually allocate, and you'd only be able to create the Clean variant from some in-memory buffer containing a serialized timestamp.

Can we make global allocator support a set-by-default feature flag of the alloc crate? Arguably the ideal would be most of the alloc stuff being in core, with a globalalloc crate. But since we can't change the name now, a feature flag might be the least confusing way to do it.

chorman0773 commented 3 years ago

I feel that situations where you would want to use dynamically allocated data structures without a global allocator are sufficiently rare that we can just tell people to use a global allocator that always panics.

Fixing this the "proper" way would require messing with default generic parameters at the language level which is likely to be very messy to implement.

Recently (and by that I mean January) C++'s Study Group 14 reviewed a paper which proposed the removal on the freestanding requirement for the global replaceable operator new to be defined by the implementation. One of the rationales was there are circumstances where an allocator is available, but that there is no single allocator which is suitable to be used as the default global, for example in operating systems. I'd argue the same principle happens here. Maybe if there is no global allocator, and the std crate is not linked, alloc::alloc::alloc panics (and define Global's alloc function to call alloc::alloc::alloc)

SimonSapin commented 3 years ago

Defaulting to all allocations panicking seems like too much of a footgun to me. However it is possible to opt into this behavior today by defining a #[global_allocator] as Amanieu mentioned. If this choice is common enough it could even be a crate on crates.io

chorman0773 commented 3 years ago

Requiring the user to provide a default, even a panicking one, seems like it could cause more issues. What if the crate gets enabled when it doesn't need to be? rust's stdlib should provide a fallback when neither a global_allocator is available, nor is libstd linked. With the aforementioned C++ paper, it wouldn't be required that a global operator new is available to make use of standard collections (if the implementation chose to otherwise provide them).

Amanieu commented 3 years ago

Note that C++'s freestanding proposal doesn't include any of the standard container types like std::vector. So it is closer to Rust's core than alloc.

chorman0773 commented 3 years ago

Indeed, though they are permitted to exist "Freestanding implementations support an implementation-defined subset of the headers, which include at least ...". Notably, the proposal to remove the requirement of the operator new does not require the implementation not support any of the container headers, or headers like <memory>. So its like core, but it can also provide similar functionality to alloc, and doesn't need a global allocation function necessarily.

lachlansneff commented 3 years ago

Is it possible to remove the default Global allocator from collections in the alloc crate in a new edition?

Or, I wonder if it's possible to drop these errors down to link time, so that if you didn't actually use the Global allocator, you wouldn't need a #[global_allocator].

phlopsi commented 3 years ago

Fixing this the "proper" way would require messing with default generic parameters at the language level which is likely to be very messy to implement.

Why would the following not work out of the box (pun intended)?

// std::boxed::Box
type Box<T, A = Global> = core::boxed::Box<T, A>;
Ericson2314 commented 3 years ago

I hope it's not mandatory. I want to program without ambient authority, full stop. (Similarly I would want to use https://github.com/bytecodealliance/cap-std, though that addresses a very different sort of ambient authority.)

shlevy commented 3 years ago

Echoing @Ericson2314, I've just switched to nightly so I can use alloc_api both to target embedded systems and to program without ambient authority. What would be needed to make this feasible?

Ericson2314 commented 3 years ago

@shlevy Maybe like https://github.com/rust-lang/rust/pull/84266 another CFG axis can be added to remove Global and friends (and hopefully they both become Cargo features).