Abstracting over mutable and immutable references

Hey, Rust beginner here. I'm trying to build a simple database in Rust and am currently working on implementing an on-disk B+-tree index. I have an in-memory representation of disk contents like so:


pub struct Page {
    data: Box<[u8]>,
}

impl Page {
    pub fn new() -> Self {
        Self {
            data: Box::new([0u8; config::PAGE_SIZE]),
        }
    }

    pub fn data(&self) -> &[u8] {
        &self.data.0
    }

    pub fn data_mut(&mut self) -> &mut [u8] {
        &mut self.data.0
    }
}

I can't take ownership of this Page struct because another component (the buffer pool) owns it. I can only access the data through read and write guards that return read-only and writable references, like so:

pub trait Guard {
    fn page(&self) -> &Page;
}

pub trait GuardMut: Guard {
    fn page_mut(&mut self) -> &mut Page;
}

Now I want to wrap this page struct into specific page implementations like BtreePage or HeapPage and add the corresponding behavior to each. What is the idiomatic way of achieving that? Do I create two separate structures like BtreePageRefView and BtreePageMutView, or introduce some generic P that I can later bound to Borrow<Page> or BorrowMut<Page>, or maybe go unsafe and just cast it to some struct on which I can define my methods and not worry about the ref/mut separation? I would appreciate any advice on this, as I'm a Rust beginner and trying to wrap my head around designs like this. Thank you!

Rust can't abstract this away. You will have to have duplicate _mut/Mut versions.

The syntax may look the same, but the behavior and restrictions of the shared and exclusive references is quite different. Usually only simple cases look the same, but you need different approaches once you actually start taking advantage of the flexibility of the shared references or deal with invariance and inability to copy exclusive ones.

4 Likes

This makes sense. Thank you.