As an exercise, I decided to write a B+ Tree without using any bespoke unsafe
code¹. It turned out to be a larger project than I expected— So far, it only supports insertion and iterating over the entire contents of the tree.
I'm thinking about turning this into a blog post to demonstrate using indexing in place of references for complex data structures. With this goal in mind, are there any code patterns in here that might cause problems for intermediate-level Rust users? (General feedback is also appreciated)
I've pasted the type definitions inline here; see the playground for full implementation details.
#[derive(Copy,Clone,Debug)]
struct BPlusInfoNode {
free: Option<NonZeroUsize>,
root: NonZeroUsize
}
#[derive(Copy, Clone, Debug)]
struct BPlusFreeNode {
next: Option<NonZeroUsize>
}
#[derive(Debug)]
struct BPlusInnerNode<K,const B:usize> {
splits: ArrayVec<K,B>,
pre: ArrayVec<NonZeroUsize, B>,
post: NonZeroUsize,
}
#[derive(Debug)]
struct BPlusLeafNode<K,V,const B:usize> {
keys: ArrayVec<K, B>,
vals: ArrayVec<V, B>,
prev: Option<NonZeroUsize>,
next: Option<NonZeroUsize>
}
#[derive(Debug)]
#[repr(align(4096))]
enum BPlusNode<K,V,const BI:usize, const BL:usize> {
Info ( BPlusInfoNode ),
Free ( BPlusFreeNode ),
Inner ( BPlusInnerNode<K,BI> ),
Leaf ( BPlusLeafNode<K,V,BL> )
}
#[derive(Debug)]
pub struct BPlusTreeImpl<K,V,const BI:usize,const BL:usize>(Vec<BPlusNode<K,V,BI,BL>>);
/// Attempt to calculate `BI` and `BL` such that each node fits within `PAGE` bytes
pub type BPlusTree<K,V,const PAGE:usize> = BPlusTreeImpl<
K,
V,
{(PAGE-4*size_of::<usize>()-align_of::<usize>()) / (size_of::<K>() + size_of::<usize>())},
{(PAGE-5*size_of::<usize>()-align_of::<usize>()-align_of::<V>()) / size_of::<(K,V)>()}
>;
impl<K:Clone+Ord+Debug,V:Debug,const BI:usize,const BL:usize> BPlusTreeImpl<K,V,BI,BL> {
// ...
}
enum InsertResult<K,V> {
Inserted,
Duplicate(K,V),
Split(K, [NonZeroUsize;2])
}
pub struct TreeIter<'tree, K, V, const BI: usize, const BL: usize> {
tree: &'tree BPlusTreeImpl<K,V,BI,BL>,
leaf: Option<NonZeroUsize>,
idx: usize,
}
¹ It's not entirely unsafe
-free because it uses Vec
and ArrayVec
, both of which use unsafe
internally.