How to resolve the problem when borrowing more than one time in Rust?

Viewed 59

I have a structure that contains a TcpStream for communication and a BytesMut for receiving data. When I need to use it to receive data, I intend to do the following.

#[tokio::test]
async fn test_refmut1() {
    struct Ctx {
        tcps: TcpStream,
        data: BytesMut,
    }
    async fn recv(ctx: Arc<Mutex<Ctx>>) {
        let mut ctx = ctx.lock().await;
        ctx.tcps.read_buf(&mut ctx.data).await.unwrap();
    }
}

Obviously, this can not compile, because tcps is borrowed once, and BytesMut, which is the read_buf() parameter, is borrrowed again.

As usual, I wrapped the other part using RefCell to get internal mutability.

#[tokio::test]
async fn test_refmut2() {
    struct Ctx {
        tcps: TcpStream,
        data: RefCell<BytesMut>,
    }
    
    async fn recv(ctx: Arc<Mutex<Ctx>>) {
        let mut ctx = ctx.lock().await;
        let tcps = &ctx.tcps;
        let mut data = ctx.data.borrow_mut();
        ctx.tcps.read_buf(&data).await.unwrap();
    }
}

However, this still doesn't compile because read_buf() requires an argument of type &mut BytesMut, which I now borrowed via RefCell as an argument of type RefMut<BytesMut>.

But I know that the two are not directly convertible, what should I do?

3 Answers

The lock() method does not provide a reference but a MutexGuard. When using this guard multiple times, we borrow it the same number of times and this can introduce the problem you report. One solution is to obtain the reference held by this guard only once (the &mut * trick, is actually .deref_mut()), then use this reference multiple times, relying on split-borrow as stated in other answers.

async fn test_refmut1() {
    struct Ctx {
        tcps: TcpStream,
        data: BytesMut,
    }
    async fn recv(ctx: Arc<Mutex<Ctx>>) {
        let mut ctx_guard = ctx.lock().await;
        let ctx = &mut *ctx_guard;
        ctx.tcps.read_buf(&mut ctx.data).await.unwrap();
    }
}

Obviously, this can not compile, because tcps is borrowed once, and BytesMut, which is the read_buf() parameter, is borrrowed again.

That's not an issue? Rust can split borrows, though sometimes you have to nudge it a bit (might depend on the edition you're using). I whipped up a quick variant and it compiles fine:

use std::io::*;

struct Ctx {
    tcps: Stdin,
    data: Vec<u8>,
}

fn recv(mut ctx: Ctx) {
    ctx.tcps.read_to_end(&mut ctx.data).unwrap();
}

fn main() {
    let ctx = Ctx { tcps: stdin(), data: Vec::new() };
    recv(ctx);
}

You can split borrow the Ctx, a clean way is to destructure a &mut version of the context:

#[tokio::test]
async fn test_refmut1() {
    struct Ctx {
        tcps: TcpStream,
        data: BytesMut,
    }
    impl Ctx {
        async fn read(&mut self) {
            let Self { tcps, data } = self;
            tcps.read(data).await.unwrap();
        }
    }
    async fn recv(ctx: Arc<Mutex<Ctx>>) {
        let mut ctx = ctx.lock().await;
        ctx.read();
    }
}

Playground

Related