Search code examples
rustmutexborrow-checker

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


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?


Solution

  • 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();
        }
    }