Is custom allocators the right abstraction?

I disagree that not moving the content is the primary motivation; I mainly see Box as going from unsized (trait / slice) to sized.

With that definition, a InlineBox<Trait, 64> is a Box that holds up to 64 bytes of data (on top of the trait pointer), and a SmallBox<Trait, 64> holds up to 64 bytes of data without memory allocation, or otherwise allocates.

I see both as useful as their InlineVec and SmallVec counterparts.

I think this doesn't add much over an allocator parameter and dedicated structs for the small/inline/etc. use cases.

The problem of dedicated structs is that you duplicate the code, again and again.

Vec is admittedly simple, and yet given all the functions it's already pretty massive. VecDeque is already slightly more complicated, HashMap is a beast.

The main advantage of an allocator parameter is that it can be changed by the caller for a whole tree of data structures, since its choice doesn't necessarily depend on the particular use of it (e.g. you may want to allocate in shared memory, or on a specific NUMA node, etc.).

This is not an either / or. There's no reason not to have a generic AllocatorStorage<A> parameterized by an Allocator.

Storages are a higher-level of abstractions than Allocators, and should be built on top of Allocators.

On the other hand whether to use Vec, SmallVec or ArrayVec very much depends on the expected size distribution of that specific vec and so it's not really useful to decide that outside the data structure.

I disagree.

I regularly deal with network protocols that have tight specifications on the maximum length of their fields, for example InlineString<24> (ie, at most 24 bytes).

It's very useful to be able to create a HashMap<InlineString<24>, MyData> without extraneous heap allocations for each and every key.

There could be some use in being generic over those classes, but this could also be accomplished by a trait encapsulating Vec's interface.

I see genericity as a completely different requirement, and indeed one for which traits are a better approach.

Here, I am for code reuse. The code in Vec is for the most part completely agnostic to the storage of the elements -- it needs some storage, and that's it.

Hence the idea of abstracting the Storage, not the collection, so that the massive amount of code in Vec can be reused "instantly" in InlineVec, SmallVec, ScratchVec, ... and whatever other storage you have need of.

So while this approach seems good for code reuse in a crate implementing those data structures, it doesn't seem clear that it should be added to std and used in std's Vec implementation.

I think it's good for code reuse even outside the crate.

The question of whether to use in std or not, I'll leave to others to decide.

I first want to raise awareness that the current path taken by std (parameterizing with an Allocator) does not lead to as much flexibility as it could, and maybe before committing ourselves to it further, we should examine whether as long as we're parameterizing all collections we might not as well extract as much flexibility as we can.

In my experience, working in high-performance systems, and I suspect in the experience of embedded engineers, the extra flexibility of Storage is definitely worth it.

16 Likes