MTB::Entity: An address-stable, interior-mutable Slab allocator
中文版请见: README-zh.md
⚠️ Notes
- This allocator is experimental; its API may change significantly.
- It has not been thoroughly tested and may contain memory-safety issues. Use with care.
- It currently supports single-threaded use only, with no plans for multithreading support.
- The recommended alternative is the
slabcrate — it’s superior in both performance and safety in general. If you don’t specifically need interior mutability, preferslab.
Introduction
The motivation for this allocator came while building Remusys. The slab crate’s Slab requires a mutable reference to allocate elements, which complicated some of my optimizations. This chunked, address-stable allocator lets you allocate new elements while reading existing ones:
use ;
/// Not every type can be stored in this allocator. You need to implement the
/// `IEntityAllocatable` trait for your type.
///
/// If you're lazy enough, you can use the new proc macro `entity_allocatable`.
Basic type hierarchy
EntityAlloc— The allocator itself; manages all chunks and the allocation/freeing of elements.IEntityAllocID<E>— A trait for converting betweenPtrIDandIndexedID.PtrID<E>— An ID pointing to an element inside the allocator, internally a raw pointer. Fast but unsafe.IndexedID<E>— An ID pointing to an element, represented by a pair of chunk index and in-chunk index. Safe but much slower.IDProxy<'alloc, E>— A proxy used for two-phase initialization of a target element.
Allocation policies
The allocator supports multiple allocation strategies to fit different scenarios. Not every type can be stored in the allocator; your type must implement IEntityAllocatable and specify a policy. Available policies:
EntityAllocPolicy128<E>— 128 elements per chunk (medium/small chunks, single-level bitmap).EntityAllocPolicy256<E>— 256 elements per chunk (medium/small chunks, single-level bitmap).EntityAllocPolicy512<E>— 512 elements per chunk (medium/small chunks, single-level bitmap).EntityAllocPolicy1024<E>— 1024 elements per chunk (large chunks, two-level bitmap).EntityAllocPolicy2048<E>— 2048 elements per chunk (large chunks, two-level bitmap).EntityAllocPolicy4096<E>— 4096 elements per chunk (large chunks, two-level bitmap).
Here’s the boilerplate for making a type allocatable, as in the example above:
use ;
Containers
Some container types are built on top of EntityAlloc for convenience:
EntityList<E>— A doubly-linked list of entities. Internally mutable; modifying the list does not requireEntityAllocto be mutable.EntityRingList<E>— A doubly-linked ring list of entities. Internally mutable; modifying the list does not requireEntityAllocto be mutable.
Auto-implementation Using #[entity_allocatable] Macro
The procedural attribute macro reduces boilerplate when implementing IEntityAllocatable.
Supported argument keys (all optional, order independent):
policy = ...Choose allocation policy. Accepted forms:- Short identifier:
Policy128,Policy256,Policy512,Policy1024,Policy2048,Policy4096 - Full type name:
EntityAllocPolicy256 - Integer literal:
128,256,512,1024,2048,4096Default:Policy256(i.e.EntityAllocPolicy256).
- Short identifier:
ptrid = TypePathUse an external custom pointer ID type you define. Must implementCopy + Eqand conversionsFrom<PtrID<Self>>andFrom<Custom> for PtrID<Self>(the macro does not generate those for external types).wrapper = IdentGenerate a transparent newtype aroundPtrID<Self>; field is public;Debugprints the raw pointer address.opaque_wrapper = IdentGenerate an opaque newtype; field is private;Debughides the pointer address (TypeName(<opaque>)).
Mutual exclusions:
ptridcannot be combined withwrapperoropaque_wrapper.wrapperandopaque_wrappercannot be used at the same time.- Repeating the same key (e.g. two
policy=) triggers a compile error.
Minimal usage
use entity_allocatable;
;
Expands to roughly:
Specify a different policy
;
or equivalently:
;
Generate a transparent wrapper type
;
// Generated:
// pub struct CPtr(pub mtb_entity_slab::PtrID<C>); // Copy, Debug prints address
// impl IEntityAllocatable for C { type PtrID = CPtr; ... }
Generate an opaque wrapper type
;
// pub struct DPtr(mtb_entity_slab::PtrID<D>); // field private, Debug hides address
// impl IEntityAllocatable for D { type PtrID = DPtr; type AllocatePolicyT = EntityAllocPolicy1024<D>; }
Use external custom PtrID type
// Your custom type (must implement the required conversions)
;
;
Generics example
Generics are supported; wrappers and policies apply per instantiation:
// impl<T> IEntityAllocatable for Node<T> { ... }
// pub struct NodePtr<T>(pub PtrID<Node<T>>); // Copy, Debug prints address
Error reporting
The macro validates conflicts (e.g. ptrid with wrapper) and repeats, and emits concise compile errors pointing to the offending argument.
When to choose wrapper vs external ptrid
- Use
wrapper/opaque_wrapperwhen you only need a thin newtype—conversion impls andDebugare generated for you. - Use
ptrid = ...when you need more control (extra trait impls, custom methods) and are willing to implement conversions yourself.
Fallback: manual implementation
You can always bypass the macro:
If the macro doesn’t cover a special scenario, open an issue or implement manually.