#[cfg(all(feature = "alloc", feature = "set"))]
use bstack::{
BStack, BStackAllocError, BStackAllocator, BStackOwnedSlice, BStackOwnedSliceAllocator,
LinearBStackAllocator,
};
#[cfg(all(feature = "alloc", feature = "set"))]
use std::io;
#[cfg(all(feature = "alloc", feature = "set"))]
use std::sync::atomic::{AtomicU64, Ordering};
#[cfg(all(feature = "alloc", feature = "set"))]
#[derive(Debug)]
enum BumpError {
Io(io::Error),
NotTail,
}
#[cfg(all(feature = "alloc", feature = "set"))]
impl std::fmt::Display for BumpError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
BumpError::Io(e) => write!(f, "I/O error: {e}"),
BumpError::NotTail => {
f.write_str("SequenceBumpAllocator: realloc only supported for the tail allocation")
}
}
}
}
#[cfg(all(feature = "alloc", feature = "set"))]
impl From<io::Error> for BumpError {
fn from(e: io::Error) -> Self {
BumpError::Io(e)
}
}
#[cfg(all(feature = "alloc", feature = "set"))]
#[derive(Debug)]
struct StampedSlice<'a> {
inner: BStackOwnedSlice<'a, SequenceBumpAllocator>,
seq: u64,
}
#[cfg(all(feature = "alloc", feature = "set"))]
impl<'a> StampedSlice<'a> {
fn start(&self) -> u64 {
self.inner.start()
}
fn len(&self) -> u64 {
self.inner.len()
}
}
#[cfg(all(feature = "alloc", feature = "set"))]
impl<'a> From<StampedSlice<'a>> for BStackOwnedSlice<'a, SequenceBumpAllocator> {
fn from(s: StampedSlice<'a>) -> Self {
s.inner
}
}
#[cfg(all(feature = "alloc", feature = "set"))]
struct SequenceBumpAllocator {
stack: BStack,
counter: AtomicU64,
}
#[cfg(all(feature = "alloc", feature = "set"))]
impl SequenceBumpAllocator {
fn new(stack: BStack) -> Self {
Self {
stack,
counter: AtomicU64::new(0),
}
}
}
#[cfg(all(feature = "alloc", feature = "set"))]
impl BStackAllocator for SequenceBumpAllocator {
type Error = BumpError;
type Allocated<'a> = StampedSlice<'a>;
fn stack(&self) -> &BStack {
&self.stack
}
fn into_stack(self) -> BStack {
self.stack
}
fn alloc(&self, len: u64) -> Result<StampedSlice<'_>, BumpError> {
let offset = self.stack.extend(len)?;
let seq = self.counter.fetch_add(1, Ordering::Relaxed);
let inner = unsafe { BStackOwnedSlice::from_raw_parts(self, offset, len) };
Ok(StampedSlice { inner, seq })
}
fn realloc<'a>(
&'a self,
handle: StampedSlice<'a>,
new_len: u64,
) -> Result<StampedSlice<'a>, BStackAllocError<'a, Self>> {
let start = handle.inner.start();
let old_len = handle.inner.len();
let end = handle.inner.end();
let seq = handle.seq;
(|| -> Result<StampedSlice<'a>, BumpError> {
if end != self.stack.len()? {
return Err(BumpError::NotTail);
}
match new_len.cmp(&old_len) {
std::cmp::Ordering::Greater => {
self.stack.extend(new_len - old_len)?;
}
std::cmp::Ordering::Less => {
self.stack.discard(old_len - new_len)?;
}
std::cmp::Ordering::Equal => {}
}
let inner = unsafe { BStackOwnedSlice::from_raw_parts(self, start, new_len) };
Ok(StampedSlice { inner, seq })
})()
.map_err(|source| BStackAllocError {
source,
handle: Some(StampedSlice {
inner: unsafe { BStackOwnedSlice::from_raw_parts(self, start, old_len) },
seq,
}),
})
}
fn dealloc<'a>(&'a self, handle: StampedSlice<'a>) -> Result<(), BStackAllocError<'a, Self>> {
let start = handle.inner.start();
let len = handle.inner.len();
let end = handle.inner.end();
let seq = handle.seq;
(|| -> Result<(), BumpError> {
if end == self.stack.len()? {
self.stack.discard(len)?;
}
Ok(())
})()
.map_err(|source| BStackAllocError {
source,
handle: Some(StampedSlice {
inner: unsafe { BStackOwnedSlice::from_raw_parts(self, start, len) },
seq,
}),
})
}
}
#[cfg(all(feature = "alloc", feature = "set"))]
fn write_and_read<A: BStackOwnedSliceAllocator>(alloc: &A, data: &[u8]) -> io::Result<Vec<u8>> {
let mut owned: BStackOwnedSlice<'_, A> = alloc.alloc(data.len() as u64)?;
owned.write(data)?;
owned.read()
}
#[cfg(all(feature = "alloc", feature = "set"))]
fn alloc_read_back<A>(alloc: &A, len: u64) -> Result<Vec<u8>, A::Error>
where
A: BStackAllocator,
A::Error: From<io::Error>,
for<'a> A::Allocated<'a>: Into<BStackOwnedSlice<'a, A>>,
{
let handle: A::Allocated<'_> = alloc.alloc(len)?;
let owned: BStackOwnedSlice<'_, A> = handle.into();
owned.read().map_err(A::Error::from)
}
#[cfg(all(feature = "alloc", feature = "set"))]
fn main() -> io::Result<()> {
let path = "custom_alloc_example.bstack";
let _ = std::fs::remove_file(path);
println!("=== SequenceBumpAllocator ===");
{
let alloc = SequenceBumpAllocator::new(BStack::open(path)?);
let a = alloc.alloc(16).unwrap();
println!("alloc seq={} offset={} len={}", a.seq, a.start(), a.len());
let b = alloc.alloc(8).unwrap();
println!("alloc seq={} offset={} len={}", b.seq, b.start(), b.len());
let err = alloc.realloc(a, 32).unwrap_err();
let a = err
.handle
.expect("failed realloc returns the original handle");
match err.source {
BumpError::NotTail => println!("realloc(a) → NotTail (original recovered)"),
other => println!("realloc(a) → {other}"),
}
let _ = a;
let b = alloc.realloc(b, 24).unwrap();
println!("realloc(b) → seq={} len={}", b.seq, b.len());
let mut owned: BStackOwnedSlice<'_, SequenceBumpAllocator> = b.into();
owned.write(b"stamped data!!\0")?;
println!("write+read → {:?}", String::from_utf8_lossy(&owned.read()?));
drop(owned);
let zeros = alloc_read_back(&alloc, 4).unwrap();
println!("alloc_read_back(4) → {:?}", zeros);
let len_before = alloc.len().unwrap();
let c = alloc.alloc(8).unwrap();
alloc.dealloc(c).unwrap();
println!(
"dealloc tail: {} → {} bytes",
len_before,
alloc.len().unwrap()
);
drop(alloc.into_stack());
}
println!("\n=== BStackOwnedSliceAllocator (LinearBStackAllocator) ===");
{
let _ = std::fs::remove_file(path);
let alloc = LinearBStackAllocator::new(BStack::open(path)?);
let data = write_and_read(&alloc, b"hello, BStackOwnedSliceAllocator")?;
println!("{:?}", String::from_utf8_lossy(&data));
drop(alloc.into_stack());
}
std::fs::remove_file(path).ok();
Ok(())
}
#[cfg(not(all(feature = "alloc", feature = "set")))]
fn main() {
println!("This example requires the 'alloc' and 'set' features.");
println!("Run: cargo run --example custom_alloc --features alloc,set");
}