error[E0502]: cannot borrow `directions` as mutable because it is also borrowed as immutable


#1

Hello Rust team,

I have read the documentation many times… but I cannot figure out how to fix that.

You many find a good way to tell me, what’s wrong and what may be done.

#[derive(PartialEq, PartialOrd, Debug)]
enum Mobility {
    #[warn(dead_code)]
    Left,
    #[warn(dead_code)]
    Right,
    #[warn(dead_code)]
    NotMobile
}

use Mobility::*;

fn create_directions( input : & Vec<i32> ) -> Vec<Mobility> {
    // all input elements are mobile leftward
    let mut directions: Vec<Mobility> = input.iter().map( |_| Mobility::Left).collect();
    // the first one is not mobile
    directions[0] = Mobility::NotMobile;

    directions
}

pub fn permute( input : Vec<i32> ) -> Vec<Vec<i32>> {
    let mut res = Vec::new();

    let mut input = input;
    input.sort();

    let mut directions = create_directions( &input );

    // push 1, -2, -3
    println!(">first permutation {:?}", input);
    res.push( input.clone() );

    let largest = find_largest_mobile_element(&input, &directions);
    println!( "input      {:?}", input );
    println!( "directions {:?}", directions );
    println!( "largest    {:?} {}", largest.0, largest.1 );
    let direction = largest.0;
    let mobile_position = largest.1;

    assert_eq!( &Left, direction );
    assert_eq!( 2, mobile_position);
    input.swap( mobile_position - 1, mobile_position );
    directions.swap( mobile_position - 1, mobile_position );



    res
}


fn find_largest_mobile_element<'a>( input: &Vec<i32>, directions : &'a Vec<Mobility> ) -> (&'a Mobility, usize) {

    if input.len() == 0 {
        return (&Mobility::NotMobile, 0);
    }

    let mut mobile_position : usize = 0;
    let mut max_value = input.get(mobile_position ).unwrap();
    let mut max_direction : &Mobility = directions.get(mobile_position ).unwrap().clone();

    for index in 1..input.len() {
        let is_mobile = directions.get(index).unwrap() != &Mobility::NotMobile;
        let current_value = input.get(index).unwrap();

        println!("----------------------------------------");
        println!("   is mobile   {}", is_mobile);
        println!("   current val {}", current_value);
        println!("   max         {}", max_value);
        println!("   index       {}", index);

        if is_mobile && current_value > max_value {
            max_value = current_value;
            mobile_position = index;
            max_direction = directions.get(index).unwrap().clone();
            println!( "max found val={}, pos={}, dir={:?}", current_value, mobile_position, max_direction);
        }
    }

    let max_direction = max_direction;

    (max_direction, mobile_position )
}

Error console

error[E0502]: cannot borrow `directions` as mutable because it is also borrowed as immutable
  --> src\lib.rs:44:5
   |
34 |     let largest = find_largest_mobile_element(&input, &directions);
   |                                                        ---------- immutable borrow occurs here
...
44 |     directions.swap( mobile_position - 1, mobile_position );
   |     ^^^^^^^^^^ mutable borrow occurs here
...
49 | }
   | - immutable borrow ends here

Many Thanks


#2

The problem is that you are not allowed to mutate “directions” as long as you hold a reference to its internals (in this case, “largest”, and more precisely “largest.0” aka “direction”).

The short-term solution is to reduce the scope of “largest” and “direction”:

    let mobile_position = {
        let largest = find_largest_mobile_element(&input, &directions);
        println!( "input      {:?}", input );
        println!( "directions {:?}", directions );
        println!( "largest    {:?} {}", largest.0, largest.1 );
        let direction = largest.0;
        let mobile_position = largest.1;

        assert_eq!( &Left, direction );
        mobile_position
    };
    assert_eq!( 2, mobile_position);
    input.swap( mobile_position - 1, mobile_position );
    directions.swap( mobile_position - 1, mobile_position );

In the longer term, Rust will be stabilizing this year a new way to handle borrows which obliviates this problem by being based on actual reference usage, as opposed to a reference being merely in scope. Google “Non-Lexical Lifetimes” or “NLL” if you want to learn more about it.


#3

@HadrienG gave you a way out of the borrow issue. I just wanted to mention that your Mobility enum is a great candidate for being Clone/Copy. You can then return (and use) it by value. In fact, std::mem::size_of::<Mobility>() < std::mem::size_of::<&Mobility>().

Only thing to keep in mind is if Mobility is part of a public API - making it Clone and/or Copy becomes part of its interface, and removing that later would be a breaking change. However, based on its variants and semantic meaning (that I’m inferring), I doubt this is a concern (i.e. it appears appropriate for Copy/Clone).


#4

Thank you for both of your helps.