Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to ensure end of immutable borrow after function call in order to enable mutable borrow?

Tags:

rust

I have come across a borrowchecker problem using Rust 2018 that I cannot find the solution to. Basically, I have a function that takes a mutable reference to a vec, and as the first part of its execution passes that same vec into another function as an immutable reference. The latter function returns a new owned value - or at least I intend it to. The problem for me is that the compiler seems to regard the immutable borrow for the function call as lasting until the end of the outer function.

Unfortunately, this isn't a problem that is solved simply by putting braces around things (it shouldn't be anyway since I'm using Rust 2018). Moreover, while I have found a number of SO questions that appear to touch on similar matters (e.g. this, this, this and this), I haven't been able to find anything else that directly addresses this problem. Or at least, nothing where I have been able to work out what I should do from it. Crucially, most other similar questions either seem to involve a reference as the return type or were only an issue before non-lexical lifetimes.

I have created an executable MVE in the Rust Playground, and the full program in case it helps. I post the code below, for reference:

// This function was blatantly borrowed from a Stack Overflow post
// but unfortunately I lost track of which one.
fn compute_mean_of_vec<'g, T>(input_vec: &'g [T]) -> T
where
    T: Copy
        + num::Zero
        + std::ops::Add<T, Output = T>
        + std::ops::Div<T, Output = T>
        + num::FromPrimitive
        + std::iter::Sum<&'g T>,
{
    let sum: T = input_vec.iter().sum();
    sum / num::FromPrimitive::from_usize(input_vec.len()).unwrap()
}

fn normalise_cost_vec<'a, T>(cost_vec: &'a mut Vec<T>)
where
    T: std::ops::SubAssign
        + Copy
        + num::traits::identities::Zero
        + std::ops::Div<Output = T>
        + num::traits::cast::FromPrimitive
        + std::iter::Sum<&'a T>,
{
    let mean = compute_mean_of_vec(cost_vec);
    for c in cost_vec.iter_mut() {
        *c -= mean;
    }
}

fn main() {
    let mut my_vec = vec![5.0f32; 5];
    normalise_cost_vec(&mut my_vec);
    for e in my_vec.iter() {
        println!("{}", e);
    }
}

The error message the compiler produces is:

error[E0502]: cannot borrow `*cost_vec` as mutable because it is also borrowed as immutable
  --> src/main.rs:26:14
   |
16 | fn normalise_cost_vec<'a, T>(cost_vec: &'a mut Vec<T>)
   |                       -- lifetime `'a` defined here
...
25 |     let mean = compute_mean_of_vec(cost_vec);
   |                -----------------------------
   |                |                   |
   |                |                   immutable borrow occurs here
   |                argument requires that `*cost_vec` is borrowed for `'a`
26 |     for c in cost_vec.iter_mut() {
   |              ^^^^^^^^ mutable borrow occurs here

Looking at the error message, it looks to me like there is probably some issue with the lifetimes specified on the two functions. I have to admit that the ones I included were pretty much just put there according to the suggestions from the compiler and Clippy, I don't fully understand them. Best as I can tell, the compiler somehow thinks that the immutable borrow in the call to compute_mean_of_vec should last for the entirety of the remainder of the call to normalise_cost_vec.

What have I done wrong, and how can I make the compiler happy? I guess it has something to do with specifying another lifetime, but I haven't been able to work out the correct approach, despite looking at The Book and a number of online resources.

like image 477
Jarak Avatar asked Mar 03 '23 15:03

Jarak


1 Answers

It seems that the problem was with the Sum trait's lifetime parameter, and here is a solution without removing this trait

fn compute_mean_of_vec<'g, T>(input_vec: &'g Vec<T>) -> T
where
    for<'x> T: Copy
        + num::Zero
        + std::ops::Add<T, Output = T>
        + std::ops::Div<T, Output = T>
        + num::FromPrimitive
        + std::iter::Sum<&'x T>,
{
    let sum: T = input_vec.iter().sum();
    sum / num::FromPrimitive::from_usize(input_vec.len()).unwrap()
}

fn normalise_cost_vec<'a, T>(cost_vec: &'a mut Vec<T>)
where
    for<'x> T: std::ops::SubAssign
        + Copy
        + num::traits::identities::Zero
        + std::ops::Div<Output = T>
        + num::traits::cast::FromPrimitive
        + std::iter::Sum<&'x T>,
{
    let mean = compute_mean_of_vec(cost_vec);
    for c in cost_vec.iter_mut() {
        *c -= mean;
    }
}

fn main() {
    let mut my_vec = vec![5.0f32; 5];
    normalise_cost_vec(&mut my_vec);
    for e in my_vec.iter() {
        println!("{}", e);
    }
}

i.e., by specifying an standalone lifetime parameter for the trait Sum, the parameter 'g won't be assumed to be carried along the whole function.

like image 84
Phosphorus15 Avatar answered May 19 '23 11:05

Phosphorus15