Internal Data Structures
Relevant source files
This document details the internal data structures that comprise the FlattenObjects container, explaining how the three core fields work together to provide efficient object storage with bitmap-based ID management. This covers the memory layout, field purposes, and structural relationships that enable the container's functionality.
For information about the public API methods that operate on these structures, see Object Management Operations. For details about memory safety and MaybeUninit usage, see Memory Management and Safety.
Core Data Structure Overview
The FlattenObjects<T, CAP> struct contains exactly three fields that work in coordination to manage object storage and ID assignment. Each field serves a distinct purpose in maintaining the container's invariants.
flowchart TD
subgraph subGraph2["External Dependencies"]
MaybeUninit["core::mem::MaybeUninit"]
Bitmap["bitmaps::Bitmap"]
end
subgraph Purpose["Purpose"]
storage["Object Storage Array"]
tracking["ID Assignment Tracking"]
metadata["Count Metadata"]
end
subgraph subGraph0["FlattenObjects"]
objects["objects: [MaybeUninit<T>; CAP]"]
id_bitmap["id_bitmap: Bitmap<CAP>"]
count["count: usize"]
end
count --> metadata
id_bitmap --> Bitmap
id_bitmap --> tracking
objects --> MaybeUninit
objects --> storage
Core Fields Summary
| Field | Type | Purpose | Size |
|---|---|---|---|
| objects | [MaybeUninit | Stores the actual objects | CAP * size_of:: |
| id_bitmap | Bitmap | Tracks which IDs are assigned | Implementation-dependent |
| count | usize | Number of currently assigned objects | 8 bytes (on 64-bit) |
Sources: src/lib.rs(L44 - L51)
Object Storage Array
The objects field is a fixed-size array that provides the actual storage space for objects. Each array slot corresponds directly to a potential ID, creating a one-to-one mapping between array indices and object IDs.
MaybeUninit Wrapper
The array uses MaybeUninit<T> rather than T directly, which allows the container to:
- Avoid default initialization of unused slots
- Safely represent uninitialized memory states
- Enable manual control over object lifecycle
flowchart TD
subgraph subGraph2["ID Mapping"]
id0["ID 0"]
id1["ID 1"]
id2["ID 2"]
idN["ID CAP-1"]
end
subgraph subGraph1["Possible States"]
uninit["Uninitialized(ID not assigned)"]
init["Initialized(ID assigned)"]
end
subgraph subGraph0["objects Array Layout"]
slot0["objects[0]MaybeUninit<T>"]
slot1["objects[1]MaybeUninit<T>"]
slot2["objects[2]MaybeUninit<T>"]
dotdot["..."]
slotN["objects[CAP-1]MaybeUninit<T>"]
end
slot0 --> id0
slot0 --> init
slot1 --> id1
slot1 --> uninit
slot2 --> id2
slot2 --> init
slotN --> idN
slotN --> uninit
Array Operations
The container performs these operations on the objects array:
- Write:
self.objects[id].write(value)- Initialize a slot with a new object - Read:
self.objects[id].assume_init_read()- Move an object out of a slot - Borrow:
self.objects[id].assume_init_ref()- Get an immutable reference - Borrow Mut:
self.objects[id].assume_init_mut()- Get a mutable reference
Sources: src/lib.rs(L48) src/lib.rs(L227) src/lib.rs(L255) src/lib.rs(L287) src/lib.rs(L322)
ID Bitmap Management
The id_bitmap field uses the external bitmaps crate to efficiently track which IDs are currently assigned. Each bit position corresponds to an array index in the objects field.
Bitmap State Representation
| Bit Value | ID State | Objects Array State |
|---|---|---|
| false(0) | ID available | MaybeUninituninitialized |
| true(1) | ID assigned | MaybeUninitcontains validT |
Key Bitmap Operations
The container uses these bitmap operations:
- Assignment Check:
self.id_bitmap.get(id)- Test if an ID is assigned - Mark Assigned:
self.id_bitmap.set(id, true)- Mark an ID as assigned - Mark Available:
self.id_bitmap.set(id, false)- Free an ID for reuse - Find Available:
self.id_bitmap.first_false_index()- Locate next available ID - Iterate Assigned:
self.id_bitmap.into_iter()- Enumerate all assigned IDs
flowchart TD
subgraph subGraph1["Corresponding Actions"]
add_op["add() operation"]
assign_op["Assignment complete"]
access_op["get()/get_mut() access"]
remove_op["remove() operation"]
ids_op["ids() iterator"]
end
subgraph subGraph0["Bitmap Operations Flow"]
find_available["first_false_index()"]
set_true["set(id, true)"]
get_check["get(id)"]
set_false["set(id, false)"]
iterate["into_iter()"]
end
access_op --> get_check
add_op --> set_true
find_available --> add_op
ids_op --> iterate
remove_op --> set_false
set_true --> assign_op
Sources: src/lib.rs(L49) src/lib.rs(L145) src/lib.rs(L223) src/lib.rs(L226) src/lib.rs(L317) src/lib.rs(L344 - L346)
Count Field
The count field maintains an accurate count of currently assigned objects, eliminating the need to iterate through the bitmap for this common query.
Count Maintenance
The count field is updated during these operations:
| Operation | Count Change | Condition |
|---|---|---|
| add() | +1 | When ID assignment succeeds |
| add_at() | +1 | When ID is available |
| add_or_replace_at() | +1 | When ID was not previously assigned |
| remove() | -1 | When ID was assigned |
Invariant Relationship
The container maintains this invariant at all times:
count == number of true bits in id_bitmap == number of initialized slots in objects
This invariant ensures that count() method calls execute in constant time rather than requiring bitmap iteration.
Sources: src/lib.rs(L50) src/lib.rs(L122 - L124) src/lib.rs(L225) src/lib.rs(L253) src/lib.rs(L291) src/lib.rs(L318)
Data Structure Coordination
The three fields work together to maintain consistency through coordinated updates. Every operation that modifies the container state updates multiple fields atomically.
sequenceDiagram
participant APIMethod as "API Method"
participant id_bitmap as "id_bitmap"
participant objectsarray as "objects array"
participant countfield as "count field"
Note over APIMethod: add(value) operation
APIMethod ->> id_bitmap: first_false_index()
id_bitmap -->> APIMethod: Some(id)
APIMethod ->> countfield: count += 1
APIMethod ->> id_bitmap: set(id, true)
APIMethod ->> objectsarray: objects[id].write(value)
Note over APIMethod: remove(id) operation
APIMethod ->> id_bitmap: get(id)
id_bitmap -->> APIMethod: true
APIMethod ->> id_bitmap: set(id, false)
APIMethod ->> countfield: count -= 1
APIMethod ->> objectsarray: objects[id].assume_init_read()
Synchronization Requirements
Since all operations are performed through &mut self, the Rust borrow checker ensures that no concurrent modifications can occur. This eliminates the need for internal synchronization primitives while maintaining data structure integrity.
Memory Layout Considerations
The struct fields are laid out in declaration order, with potential padding for alignment:
objects: [MaybeUninit<T>; CAP]- Largest field, aligned toT's alignmentid_bitmap: Bitmap<CAP>- Size depends onCAPand bitmap implementationcount: usize- 8 bytes on 64-bit platforms
The total memory footprint is approximately CAP * size_of::<T>() + bitmap_size + 8 bytes, making it suitable for resource-constrained environments when CAP is chosen appropriately.
Sources: src/lib.rs(L44 - L51) src/lib.rs(L222 - L232) src/lib.rs(L315 - L326)