If it's possible using allocator "just" for a crate?

Currently, once a crate uses an allocator, all other crates can no longer use the allocator. Can different crates use different allocators?

If not, does rust provide some kind of api to change the allocator of the container? like C++ containers

It's currently nightly, but you'll notice when looking at things that allocate that they have a special A: Allocator type argument. An example is Box:

pub struct Box<T, A = Global>(_, _)
where
    A: Allocator,
    T: ?Sized;

You can pick something else by using the _in methods such as new_in, and providing an instance of your allocator.

1 Like

It looks like string can only use the global allocator?

Indeed. No clue why though -- maybe someone who's worked/working on the alloc crate could answer that?

Adding custom allocator support to std types is a gradual, in-progress effort.

1 Like

String doesn't have an allocator parameter. Wouldn't it be a breaking change to make it generic? It certainly feels a big leap that can cause subtle breakage, even if a default type parameter is added.

IIRC adding a type parameter to String could not cause any breakage, as the default nullary type constructor would still exist and any usage of the unary type constructor is a compile error today. I also don't think it could cause 'subtle breakage', but would certainly have at least one negative aspect: every single type that stores something allocated would need to be generic over it, including every single type that currently stores owned Strings. (It is needless to say that only a minority of crates do not have such a type, somewhere.)

This is why I strongly prefer a solution based on this blogpost by @yoshuawuyts - it would not only fit into the current Rust allocation system pretty well, but also work just as good as the <A> model without the gigantic new API surface.

The same change was made to Box, Vec, etc. without breaking code. Notice that the following compiles:

struct Foo<T = ()>(T);

type Bar1 = Foo;
type Bar2 = Foo::<>;

And in type language, as @leob points out, the nullary type constructor would still exist similar to how in Box, the unary type constructor exists alongside the binary type constructor.

Those were already generic, though.

I'm well aware; my worry is that there is some other subtle difference between a generic and a non-generic type. Not for theoretical, fundamental reasons, but for technical reasons such as implementation details of the compiler. I hope I'm wrong, but the possibility seems entirely realistic given the number of moving parts around generics.

Very intriguing article. Reminds me of pythons context managers. It would certainly be nice to not clutter everything with new_in() methods.
But wouldn't storing strings in structures still work anyway without adding new generic parameters, because you can just use the default parameter?

This is true, but every library that stores Strings should likely be generic over the allocator used in order to make every crate compatible with non-system allocation - even if you have a Window struct that just stores a description: String, the user of the library should be able to specify how that string should be allocated. The point of the Allocator trait is to make custom allocation work, and I think doing this universally (i.e., via context handlers) serves this purpose better than having type parameters everywhere.

Oh no. :scream::no_entry: Absolutely no more implicit magic context, please. #[global_alloc] is already special and bad enough.

Every time I see people complain that they have to… wait for it… pass arguments to functions, because it's so annyoing, I begin to wonder what other comparable expectations they have about programming… should code also write, test, and deploy itself?

I feel like a crater run would answer that problem quite nicely.

2 Likes

I find it a bit unfair to dismiss this idea as "magic". From what I have experienced, there are almost no good reasons to ever not be generic over an allocator, and thus, at least for me, ambient allocation is the right way forward.

Unlike function arguments, allocation is something you might always have to do later. You might want to improve your error diagnostics and use format! instead of string literals, use a Vec instead of an array etc. And I believe making this allocator-agnostic should be as simple as possible: the best way would be to not require any additional code for library authors, making them able to opt-out by explicitly resorting to System or Global.

I agree that one should always be skeptical of ideas even slightly tangential to global variables. But I also think that this way of programming might even reduce dependence on the global allocator by having to explicitly mention an allocator for a scope in which to allocate with in a future edition. This is not about functions not having to receive arguments (or it being "annoying"), but about a parameter that might always possibly be needed at a later point in time, if just for reasons of restructuring code.

2 Likes

But isn't a custom allocator something that you usually only need for specialized purposes and in isolated parts of the code that are written with a specialized allocator in mind?
I mean otherwise you can just switch out the global allocator and be done.

1 Like

The way I think about custom allocators is that you typically want to tie them to the equivalent of a "main loop" somewhere.

For example we might want to use a slab allocator in a request handling loop if we know the max allocation size of each request. That would effectively amortize the allocation cost for all requests.

Or perhaps you're doing some kind of batched processing over multiple threads, and it could make sense to use a bump allocator for each thread - resetting the allocator's state after each item is processed. That would amortize the allocation cost for all workloads across all threads.

Right now using custom allocators is not easy, which is probably one reason why they're not in widespread use. I believe that if we made them easier to use, we'd see them used more.

2 Likes

Fundamentally no, because you can pass Vec<u32>s between crates, and thus they have to be using the same allocator.

To have them use different allocators mean they need to be different types, as other comments have described.

1 Like

Using contextual allocators as described in that blog post has a similar issue as with using a custom allocator "just for a crate" — either you can't move any allocator-context-using type across a context-with block, because it would be interpreted as using the wrong allocator (unsound), or all use of the allocator needs to be the same type: dynamic.

If all of your allocation is dynamic, you can do that with #[global_allocator], on stable, today. Set a thread local &'static dyn GlobalAlloc to use for alloc, prefix every alloc with said reference, then offset and use that reference to handle realloc and dealloc requests.

Contexts as contextually semi-implicit arguments could work. This could even unify new and new_in, if contexts can be themselves generic. But types can't use contexts, only functions can.

If you have potentially alloc-owning types declare a with alloc context, then you've just made the API generic again, except worse, because now there's no good way to talk about a type with a different context. We already have a way to talk about types instantiated for specific contexts: type generics. And with context alloc you can still just as easily forget to forward the genericism and end up just using Global if there's still a default global allocator context available when not declaring a with alloc context.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.