Borrow Checking and Creating Mutually Exclusive Branches

Hello Rustaceans,

Recently I encountered an issue that I've had a difficult time finding other occurrences online and hoped that I could request assistance from you all, the great masters of this language. I've declared my function compute_on_device as follows:

fn compute_on_device(
    &mut self,
    id: u32,
) -> Result<(&GPUSubbufferHandle, Vec<TransferType>), TransferErr> {
    if let Some(gpu_subbuffer_handle) = self.gpu_subbuffer_handles.get(&id) {
        return Ok((gpu_subbuffer_handle, Vec::default()));
    }

    if let Some(mm_subbuffer_handle) = self.mm_subbuffer_handles.remove(&id) {
        let transfer_cmds = self.move_mm_to_gpu(id, mm_subbuffer_handle)?;
        let gpu_subbuffer_handle = self
            .gpu_subbuffer_handles
            .get(&id)
            .ok_or(TransferErr::LogicError)?;
        return Ok((gpu_subbuffer_handle, transfer_cmds));
    }

    if let Some(disk_handle) = self.disk_subbuffer_handles.remove(&id) {
        let transfer_cmds = self.move_disk_to_gpu(id, disk_handle)?;
        let gpu_subbuffer_handle = self
            .gpu_subbuffer_handles
            .get(&id)
            .ok_or(TransferErr::LogicError)?;
        return Ok((gpu_subbuffer_handle, transfer_cmds));
    }

    // Id wasn't found
    Err(TransferErr::LogicError)
}

In this example I'm working with a resource I'm tracking, Vulkano subbuffers which could be in one of three locations for me: my gpu resource store, my host memory resource store, or my disk resource store. These branches look to me to be mutually exclusive, I've defined them in such a way that if a resource is found in one of these stores then control enters the associated block and only exits when returning a value.

Currently I'm being blocked by the borrow checker. My usage of the elided lifetime here conflicts in a way that I don't understand. When I get an immutable borrow from self.gpu_subbuffer_handles the resulting value is said to be borrowed to the borrow checker past the following return, which conflicts with the would-be mutable borrows later needed for my self.move... functions.

Does anyone have recommendations for how I could more clearly enunciate the lifetime I'm using here to assure the borrow checker that my function is memory safe?

It's probably due to conditional return of a borrow, though it's hard to be completely sure from the code alone. You can try recreating the borrow inside the if block if it's not too costly...

    if self.gpu_subbuffer_handles.get(&id).is_some() {
        let gpu_subbuffer_handle = self.gpu_subbuffer_handles.get(&id).unwrap();
        return Ok((gpu_subbuffer_handle, Vec::default()));
    }

There's also this crate, intended as a stop-gap until the improved borrow checker lands. It also has (a collapsed) section on "non-unsafe albeit cumbersome workarounds for lack-of-Polonius issues" that you could explore.

Seeing the error of cargo run from the terminal and the method signatures involved may improve my confidence of this reply.

Thank you for helping me to understand the underlying issue here, "the conditional return of a borrow" wasn't an issue I was aware of, and you've greatly helped me. Until Polonius lands the second .get() is a good way to get around this issue. Thank you again for the help!

1 Like

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.