[alloc] Add BStackVec for typed, growable vector backed by a BStack allocation#2
[alloc] Add BStackVec for typed, growable vector backed by a BStack allocation#2williamwutq wants to merge 19 commits into
BStackVec for typed, growable vector backed by a BStack allocation#2Conversation
|
Original entry in PLANNED.md: Adding
|
The Feature Checklist
|
|
@copilot check the The Feature Checklist according to the PR description. Comment on this Github PR with an edited version of the checklist where items that are satisfied are marked. Give additional comments on how to implement/satisfy some of the items that might take effort, if necessary. |
Reviewed on
High-effort follow-ups I recommend next:
|
BStackVec for typed, growable vector backed by a BStack allocationBStackVec for typed, growable vector backed by a BStack allocation
| /// | `push` (no realloc) | write element → increment `len` | Crash after element write: element on disk but `len` not updated; slot is effectively invisible. Re-running `push` with the same value recovers correctly. | | ||
| /// | `push` (with realloc) | `realloc` → write `cap` → write element → increment `len` | Crash at any point: header re-read on next open reflects the committed state; worst case is a leaked block (with `LinearBStackAllocator`) or a stale cap value. | | ||
| /// | `pop` | read element → zero slot → decrement `len` | Crash after zero but before `len` decrement: element is zeroed on disk but `len` still counts it; `get(len-1)` returns a zero-bit `T`. Decrementing `len` on the next `pop` call removes it cleanly. | | ||
| /// | `truncate` | zero removed slots → write `len` | Crash after zeroing but before `len` write: logically removed elements are on disk as zeros; `len` still points past them. Calling `truncate` again to the same target is idempotent. | | ||
| /// | `resize` (grow) | `reserve` → write elements → write `len` | Same as `push` repeated; elements between the old and new `len` may be partially written. | | ||
| /// | `clear` | (delegates to `truncate(0)`) | See `truncate`. | | ||
| /// | `reserve` | `realloc` → write `cap` | Crash between the two: cap field may reflect the old value; the block is larger than cap indicates. Harmless — the next `push` re-checks and may realloc again unnecessarily. | |
There was a problem hiding this comment.
Do not refer to concrete allocators such as LinearBStackAllocator in these comments
| /// Allocate a `BStackVec` and populate it from a Rust slice. | ||
| /// | ||
| /// The resulting vec has `len == capacity == data.len()`. | ||
| pub fn from_slice(data: &[T], alloc: &'a A) -> io::Result<Self> { | ||
| let len = data.len() as u64; | ||
| let slice = alloc.alloc(Self::block_size(len)?)?; | ||
| let vec = Self { | ||
| slice, | ||
| _phantom: PhantomData, | ||
| }; | ||
| if len > 0 { | ||
| // Write len and cap; elements are written individually below. | ||
| vec.write_header(len, len)?; | ||
| for (i, &item) in data.iter().enumerate() { | ||
| vec.write_elem_at(i as u64, item)?; | ||
| } | ||
| } | ||
| Ok(vec) | ||
| } |
There was a problem hiding this comment.
We have from_slice, we might need to add read_all or read_vec that reads this as a rust Vec<T>.
There was a problem hiding this comment.
Pull request overview
This PR introduces BStackVec, a public, typed, growable vector abstraction backed by a BStack allocation (behind alloc + set), alongside documentation and examples to support the new container API.
Changes:
- Adds
BStackVecandBStackVecIterimplementation undersrc/alloc/vec.rsand re-exports under the appropriate feature gates. - Updates crate/module/README documentation and CHANGELOG to describe the new API and its crash-recovery model.
- Adds a
bstack_vecexample and updatesCargo.tomlexample feature requirements.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
src/lib.rs |
Documents BStackVec under feature flags and re-exports it when alloc + set are enabled. |
src/alloc/vec.rs |
New BStackVec/BStackVecIter implementation plus unit tests. |
src/alloc/mod.rs |
Adds vec module and exports BStackVec/BStackVecIter behind set (within alloc). |
README.md |
Extends feature documentation and adds a BStackVec section with layout/usage. |
PLANNED.md |
Removes the now-implemented “Adding BStackVec<T>” planned item. |
examples/bstack_vec.rs |
New example demonstrating persistence/reopen flow using BStackVec. |
CHANGELOG.md |
Adds an Unreleased entry describing BStackVec. |
Cargo.toml |
Registers examples with required-features. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| /// reconstruct the handle after a reopen without any additional recovery logic. | ||
| /// | ||
| /// ## Feature flags | ||
| /// | ||
| /// Requires both the `alloc` and `set` Cargo features. | ||
| pub struct BStackVec<'a, T: Copy, A: BStackSliceAllocator> { | ||
| /// The full block: header (16 B) followed by element data. | ||
| slice: BStackSlice<'a, A>, | ||
| _phantom: PhantomData<T>, | ||
| } |
| /// Remove and return the last element, or `None` if empty. | ||
| /// | ||
| /// The vacated slot is zeroed before `len` is decremented. | ||
| pub fn pop(&mut self) -> io::Result<Option<T>> { | ||
| let (len, _) = self.read_header()?; | ||
| if len == 0 { | ||
| return Ok(None); | ||
| } | ||
| let value = self.read_elem_at(len - 1)?; | ||
| self.zero_elem_at(len - 1)?; | ||
| self.write_len_field(len - 1)?; | ||
| Ok(Some(value)) | ||
| } | ||
|
|
||
| /// Shorten the vec to `new_len` elements. | ||
| /// | ||
| /// No-op when `new_len >= len`. Removed slots are zeroed in a single | ||
| /// [`BStackSlice::zero_range`] call; capacity is unchanged. | ||
| pub fn truncate(&mut self, new_len: u64) -> io::Result<()> { | ||
| let (len, _) = self.read_header()?; | ||
| if new_len >= len { | ||
| return Ok(()); | ||
| } | ||
| let start = Self::elem_offset(new_len); | ||
| let removed_bytes = (len - new_len) * Self::elem_size(); | ||
| self.slice.zero_range(start, removed_bytes)?; | ||
| self.write_len_field(new_len) | ||
| } |
There was a problem hiding this comment.
The recommendation is to reverse the order so len change comes before zeroing so post-crash never deserialize zeroed slots that should be cleaned
| /// Return the underlying block slice (header + all allocated element space). | ||
| /// | ||
| /// This is the original allocation handle and may be passed to | ||
| /// [`crate::BStackAllocator::realloc`] or [`crate::BStackAllocator::dealloc`]. | ||
| pub fn raw_block(&self) -> BStackSlice<'a, A> { | ||
| self.slice | ||
| } | ||
|
|
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
BStackVec
Description: Typed, growable vector backed by a
BStackallocation.New Feature: Yes; Public Facing
Important Feature: Yes
Type: Container
Feature Flags: alloc, set
Breaking change: No
New Types:
BStackVecIter,BStackVecRust Only: No
Fuzz: Not needed