Generic Types over Statics

I have been thinking about the multiple allocator problem, if that is what I may call it.


Problem statement:

Currently in Rust there can only ever be 1 global allocator and every type either uses it or must manually choose another one. If a different one is chosen then care has to be made to make sure that the correct allocator is sent the free message.

Possible solution:

If types were generic over a static that implements a type (say core::alloc::Alloc) then there could easily be many allocators in a rust program without conflicting.

Syntax bikeshed:

struct Box<T, A>
where
    T: ?Sized,
    Allocater: static core::alloc::Alloc + default = std::alloc::System
{
    inner: NotNull<T>,
}

Because this generic static has a default value it does not need to be provided when used. However, it can be used.

A generic static does not take any space in an instance of that type since the compiler knows the type -> static mappings. An implementer can then then reference that static as if it was an Associated Type.

// Generic statics are never referenced in `impl` blocks, 
// only at construction location.
impl<T: ?Sized> Box<T> {
    fn new() -> Self {
        Self {
            inner: Self::Allocater.alloc_one().unwrap(),
        }
    }
}

I did some cursory looking to see if something like this had been suggested before. And of course this could be useful in other contexts besides allocation. It was just the one that I felt was the most natural fit.

AFAIK this has always been part of the plan for https://github.com/rust-lang/wg-allocators

This WG aims to:

  • Define traits and other APIs for memory allocators, with the unstable std::alloc::Alloc trait as a starting point.
  • Make collections such as Vec<T> able to use any allocator, most likely through an additional type parameter.

It just hasn't been talked about explicitly that much because the interesting, difficult problems all seem to be on the Alloc traits.

Ah thanks

See things like

I cannot seem to find what Global is or what the equals sign in A: AllocRef = Global means.

And this does seem to be very close to what I mean, except that the a: A wouldn't be needed.

If you don't identify the responsible allocator, then how at runtime could the current Vec be deallocated, either at end of life or because a new different-capacity Vec needed to be allocated? How would the allocator for the latter be identified at runtime?

Good question.

I believe that it would be in a similar way that the compiler knows which drop method to call now (per type). The only difference is that the allocator would be change depending on the type. And since it doesn't make sense to change allocators during execution it should be quite possible that the compiler knows which one to use.

That is why I used the term static. Because I wanted to make it clearer that each allocator would be a single instance, "global" to the whole program with the caveat that two types could use different allocators.

So basically, the proposal is for the addition of "static generics" (by analogue to const generics), where the reference to a static becomes part of the type.

The current pattern (take an actual value) is equivalently (more) powerful, as you can give it a zero sized type (such as Global or System) which will then statically dispatch to your global.

The only advantage of a "static generic" would be requiring the generic is a zero-sized type-system reference rather than also having the capability of storing inline info. (As well as skipping the member name, I suppose.)

1 Like

I didn't know that the trait = type existed.

Yes I think this might be equivalent. Another advantage would be not needing to wrap access to the static with a ZST that just calls the static (in the case where the actual implementation needs some data)

Its more like, generic_param: bounds = default_type where : bounds is optional

So all of the useful types in std, and in other existing crates (at least up until a major semver change), would forever be tied to the global allocator, since only new types can be tied to the new allocator.

No, I was figuring that this parameter would be optional (if a default was provided). So adding something like this would be even a semver minor change.

This default doesn't seem to be optional though.

Here is what I concocted to try it out. https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=2b1d2e4d12ef8600f7a165c50e84ab7f

default type parameter, so you can write MyVec<Foo> instead of MyVec<Foo, MyAllocator>. I agree with @CAD97, that static generics is strictly less general and useful than the current setup. You can use zero-sized types to perfectly emulate static generics with the added benefit that you can also have stateful local allocators.

Okay, I guess my only major concern with the current arrangement is that you have to specify the field even if it has a default Type set to the bounds, which makes adding such a thing a semver-major change.

How? if you have a single private field, adding new fields is not a semver-major change. And if you are dealing with an allocator, you likely do have at least 1 private field.

Importantly, this means you could staple Box, Arc, and (if you're insane) Vec to an arena.

3 Likes

I see. And further testing shows that you can indeed impl without specifying that default-ed bound