0

I'm solving a problem from Leetcode and encountered the fact that Rust won't let me execute it efficiently. What am I doing wrong? I know about the book article about references and borrowing and would like to know how to solve this problem despite the peculiarities of the language.

I am trying to create one reference for a vec that should change and another for a vec that will not change. Rust won't let me do that. The program works, but only when using .clone(), which will be very slow and not necessary (last_row does not change anywhere, only the values are derived from there).

Here is the working code:

use std::cmp;

fn minimum_total(mut triangle: Vec<Vec<i32>>) -> i32 {
    for i in (0..triangle.len()-1).rev() { // from penultimate (last - 1) to first
        let last_row = & triangle[i+1].clone();
        let current_row = &mut triangle[i];
        for j in 0..current_row.len() {
            current_row[j] = cmp::min(last_row[j], last_row[j+1]) + current_row[j];
        }
    }

    triangle[0][0]
}

fn main() {
    println!("{}", minimum_total(vec![vec![2],vec![3,4],vec![6,5,7],vec![4,1,8,3]]));
}

As you can see, I used .clone() to fix the borrow checker errors that show up when you try to write a program using references:

use std::cmp;

fn minimum_total(mut triangle: Vec<Vec<i32>>) -> i32 {
    for i in (0..triangle.len()-1).rev() { // from penultimate (last - 1) to first
        let current_row = &mut triangle[i];
        let last_row = &triangle[i+1];
        for j in 0..current_row.len() {
            current_row[j] = cmp::min(last_row[j], last_row[j+1]) + current_row[j];
        }
    }

    triangle[0][0]
}

fn main() {
    println!("{}", minimum_total(vec![vec![2],vec![3,4],vec![6,5,7],vec![4,1,8,3]]));
}

Terminal:

error[E0502]: cannot borrow `triangle` as immutable because it is also borrowed as mutable
 --> src\main.rs:6:25
  |
5 |         let current_row = &mut triangle[i];
  |                                -------- mutable borrow occurs here
6 |         let last_row = &triangle[i+1];
  |                         ^^^^^^^^ immutable borrow occurs here
7 |         for j in 0..current_row.len() {
  |                     ----------------- mutable borrow later used here

For more information about this error, try `rustc --explain E0502`.

However, when trying to write a program poorly everything works without any problems:

use std::cmp;

fn minimum_total(mut triangle: Vec<Vec<i32>>) -> i32 {
    for i in (0..triangle.len()-1).rev() { // from penultimate (last - 1) to first
        for j in 0..triangle[i].len() {
            triangle[i][j] = cmp::min(triangle[i+1][j], triangle[i+1][j+1]) + triangle[i][j];
        }
    }

    triangle[0][0]
}

fn main() {
    println!("{}", minimum_total(vec![vec![2],vec![3,4],vec![6,5,7],vec![4,1,8,3]]));
}
Chayim Friedman
  • 47,971
  • 5
  • 48
  • 77
mxkmn
  • 79
  • 1
  • 6
  • 2
    your "poor" code generates smaller assembly too. do you mean because you prefer the variable names rather than `triangle[][]`? – Jeremy Meadows Jun 20 '22 at 19:23
  • 1
    Cloning is the right way to go. You are trying to iterate through `triangle` while editing it using two different references. Your double for loop code is probably the better way to do it. Just because it is a double for loop does not mean it is automatically worse – sami-amer Jun 20 '22 at 19:25
  • @rodrigo The two extra lines make the code in the first listing easier to analyze, read and modify. Of course, there will be no difference in the speed of execution, but I'm frustrated by the lack of ability to write code that is not only safe, but also of high quality. – mxkmn Jun 20 '22 at 19:27
  • @JeremyMeadows Yeah, I just want to access the data (`triangle[]`, not `triangle[][]`) via variable names. I understand that this will not increase the speed of the code compared to direct accesses. – mxkmn Jun 20 '22 at 19:31
  • @mxkmn that's understandable, it won't have a noticeable speed difference or anything, but everybody has a different definition of "better" so I was curious. I also agree with sami-amer's comment in that `clone` is a fine solution. – Jeremy Meadows Jun 20 '22 at 19:47
  • 1
    Please don't edit the answer into the question! Instead, just accept the answer that helped you (which you did), or write an answer to your own question (yes, [you may do that](/help/self-answer)) to share the solution you ended up using. – FZs Jun 21 '22 at 12:02
  • 1
    @FZs You can rollback in those cases. – Chayim Friedman Jun 22 '22 at 00:40
  • @FZs How can I better add a response in case I closed my question? – mxkmn Jun 23 '22 at 13:08
  • You don't. If this is a duplicate, then the answer is already in the duplicate, and adding it is a noise. – Chayim Friedman Jun 23 '22 at 15:51

2 Answers2

2

You can accomplish this via the split_at_mut() method, which comes from the primitive slice type (which Vec auto-derefs to). This method allows you to safely take a mutable slice and split it into two mutable slices at a given index, since it's guaranteed that the two slices won't overlap. (Note this is zero-copy, as slices are just fat pointers borrowing an existing contiguous sequence.)

The two slices then are independent for the purposes of borrow checking, so you can borrow mutably from both slices at the same time (or, in your case, mutably from one and immutably from the other).

use std::cmp;

fn minimum_total(mut triangle: Vec<Vec<i32>>) -> i32 {
    for i in (0..triangle.len()-1).rev() { // from penultimate (last - 1) to first
        let (left, right) = triangle.split_at_mut(i + 1);
        
        let current_row = left.last_mut().unwrap();
        let last_row = right.first().unwrap();
        
        for j in 0..current_row.len() {
            current_row[j] = cmp::min(last_row[j], last_row[j+1]) + current_row[j];
        }
    }

    triangle[0][0]
}

fn main() {
    println!("{}", minimum_total(vec![vec![2],vec![3,4],vec![6,5,7],vec![4,1,8,3]]));
}
cdhowie
  • 158,093
  • 24
  • 286
  • 300
0

Yes, that's the thing with Rust -- you have to code in a way that the compiler can tell it is safe. Sometimes that requires a bit of thought, but often in the end you have code that is cleaner than you would have written otherwise.

Imagine having a function that could walk through items two at a time, calling a function you specify on them, with the first being immutable, and the second being mutable. Call it pairs_mut, and calling it with function f on a,b,c,d it would result in calls to f(&a, &mut b), f(&b, &mut c), and f(&c, &mut d). A non-generic version is not that hard to write. I am hesitant to put the code here because you are trying to learn from the exercise.

NOTE: I suspect that such a facility (or perhaps something more general) exists somewhere in the Rust ecosystem, but I have looked in Iterator and the itertools crate and didn't find anything. If you know of an existing facility like this, please share a link in a comment. Otherwise perhaps I should try to get something added to itertools.

Now given pairs_mut, I hope you can see that minimum_total could run it on triangle.rev() and do that bit of dynamic programming to come up with the minimum sum. Let me know if you want me to put some actual code here, but I encourage you to try it yourself first.

AmigoNico
  • 6,652
  • 1
  • 35
  • 45