Modifying vector value according conditions - idiomatic way?


#1

Hi, so this might be a quite newbie question, but nevertheless I did not find sensible answer. Given that I’m given a vector of values v, let’s assume 5 of them. I need to modify the middle one based on conditions regarding the whole array. A pseudo code would look like this:

let mut v = vec![Some(1), None, Some(2), Some(3), None];
    match v.get_mut(2){
        Some(&mut Some(ref mut x)) => {
            match v.get(0) {
                Some(& Some(ref y)) => {
                    if *y < 10{
                        *x = v.get(3).unwrap().unwrap() + 5;
                    } else {
                        *x = v.get(3).unwrap().unwrap() + 2;
                    }
                },
                _ => ()
            }
        },
        _ => ()
    }

Of course this will not compile for the obvious reasons of borrowing. My question is, what is the proper way to this in rust as I could not figured it out myself so far. I thought of 3 possible solution, but I think both are ugly:

  1. For each match copy the value from the vector and use that inside the match, so that you are not really borrowing v
  2. Make boolean variables which describe which path you take, use the match with borrows only to set these variables
    The third solution is sort of presented below:
let mut v = vec![Some(1), None, Some(2), Some(3), None];
    if match v.get(2){
        Some(&Some(_)) => true,
        _ => false
    } && match v.get(0) {
        Some(&Some(ref y)) => true,
        _ => false
    } {
        v[2] = if v.get(0).unwrap().unwrap() < 10 {
            Some(v.get(3).unwrap().unwrap() + 5)
        } else {
            Some(v.get(3).unwrap().unwrap() + 2)
        };
    };

So I after all this I wanted to know if you guys have any suggestions for better ways, and how to think about this in rust. Cheers!


#2

I think that instead of using Vec::get you can use [] operator here:

fn main() {
    let mut v = vec![Some(1), None, Some(2), Some(3), None];
    if v.len() >= 4 {
        v[2] =  {
            let increment = if v[0].map(|x| x < 10).unwrap_or(false) { 5 } else { 2 };
            Some(v[3].unwrap() + increment)
        };
    }
}

#3

Oh, so does the [] operator perform a copy then? Or otherwise why it is not borrowing the vector?


#4

[] returns a reference. There is no implicit non trivial copying in Rust at all. I think that when you do

v[i] = calculate(v[i], v[j], v[k])

then you don’t need to borrow v mutably while you are performing calculate. In other words, this can be rewritten as

let new_value = {
    // borrow `v` immutably several times
    let increment = if v[0].map(|x| x < 10).unwrap_or(false) { 5 } else { 2 };
    Some(v[3].unwrap() + increment)
}; 

// immutable borrows of `v` end

v[2] = new_value;  // borrow `v` mutably. 

#5

Ok I think I understood. To some extend, and please correct me if wrong, actually my main problem was that the match makes a immutable borrow of v which prevents me from mutably changing it. So in fact very similar to your code, but with using the get for checks, this compiles:

v[2] = match v.get(2) {
        Some(&Some(val1)) => match v.get(0) {
              Some(&Some(val2)) => if val2 < 10{Some(val1 + 5)} else {Some(val2 + 2)},
            _ => v[2]
        },
        _ => None
    };

I think I get it now from your explanation, the proper way is to first compute the value in a separate scope and then assign it, thus destorying all of the borrows needed for the computation inside that scope.


#6

Yest, that is basically it, except that the borrow in match v.get_mut(2) is mutable, so it prevents all subsequent borrows.