How to hold mutable references to parts of a buffer in different structs?

Hi everyone,

I'm new to Rust and trying to achieve the following but am unsure if it's even possible due to the borrow rules.

I have two structs:

A: This struct contains a Vec (or another byte buffer).
B: This struct should hold a mutable reference to a slice of A's buffer, with accessors and mutators to read and modify the slice.
In the full version, I plan to have multiple structs like B, each responsible for different parts of the buffer. Some of these structs may even delegate responsibility to others.

However, Rust's borrow checker doesn’t seem to allow this. Is there an elegant way to achieve something like this?

Thanks for your help!

As described this is a recipe for borrow-related pain.
In general you don't want to keep mutable references in a Vec; as you discovered, this doesn't yield happy results.

But what you could do instead of a Vec<&mut T> is keep a Vec<std::sync::Arc<std::sync::Mutex<T>>>.

This is possible if you do not reallocate the buffer in A. You can use [T]::split_at_mut to separate the borrows. Note that this will mutably borrow A as long as any B exist which means you cannot access any fields of A.

2 Likes

Citation needed. This is just like slice::chunks_mut(), which is clearly allowed (and sound). Proof.

Not if you borrow directly from a field only.

2 Likes

Depending on what exactly the usage patterns you envision are, this all still is possible with simple borrows.

use std::slice::SliceIndex;

#[derive(Debug)]
struct A {
    buffer: Vec<u8>,
}
impl A {
    // construct a struct `B` that does
    // hold a mutable reference to a slice of A's buffer
    // (well, in this case, the 'slice' is the *entire* buffer)
    fn b(&mut self) -> B<'_> {
        B {
            r: &mut self.buffer,
        }
    }
}

#[derive(Debug)]
struct B<'a> {
    r: &'a mut [u8],
}

impl B<'_> {
    // example 1
    // how to turn one `B` struct into multiple
    // each responsible for different parts of the buffer
    fn split_at(self, i: usize) -> (Self, Self) {
        let (left, right) = self.r.split_at_mut(i);
        (B { r: left }, B { r: right })
    }
    // example 2
    // delegate in a re-borrowing manner
    fn delegate_slice(&mut self, i: impl SliceIndex<[u8], Output = [u8]>) -> Option<B<'_>> {
        Some(B {
            r: self.r.get_mut(i)?,
        })
    }
    // example 3
    // delegate by giving up part of the borrow with its full lifetime
    // exercise: learn lifetime elision rules, then explain the difference that the usage 
    // of `Self` here makes, compared to usage of `B<'_>` in the return type of `delegate_slice`
    fn delegate_tail_forever(&mut self, i: usize) -> Self {
        let r = std::mem::take(&mut self.r);
        let (r, tail) = r.split_at_mut(i);
        self.r = r;
        B { r: tail }
    }
}

fn main() {
    let mut a = A {
        buffer: vec![1, 2, 3, 4, 5],
    };
    let mut b = a.b();
    let (delegate1, mut delegate2) = b.delegate_slice(..4).unwrap().split_at(2);
    dbg!(&delegate1, &delegate2); // [1, 2], [3, 4]

    delegate1.r[0] = 100;
    delegate2.r[0] = 200;
    dbg!(&delegate1, &delegate2); // [100, 2], [200, 4]

    let delegate3 = delegate2.delegate_tail_forever(1);
    dbg!(&delegate2, &delegate3); // [200], [4]

    delegate2.r[0] += 10;
    delegate3.r[0] += 10;

    dbg!(&a); // [100, 2, 210, 14, 5]
}

Rust Playground

However, perhaps you have usage patterns in mind that won’t be supported by this.

2 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.