0

All I wanna do is to swap the first and the last element in Vec. So I wrote this:

// getting a vector of integers from stdin:
let mut ranks = std::io::stdin()
    .lock()
    .lines()
    .next()
    .unwrap()
    .unwrap()
    .trim()
    .split_whitespace()
    .map(|s| s.parse::<usize>().unwrap())
    .collect::<Vec<usize>>();
// ...
// pushing something to ranks
// ...
ranks.swap(0, ranks.len() - 1); // <------ TRYING TO SWAP

And of course, it doesn't work, because

error[E0502]: cannot borrow `ranks` as immutable because it is also borrowed as mutable
 --> src\main.rs:4:19
  |
4 |     ranks.swap(0, ranks.len() - 1);
  |     --------------^^^^^^^^^^^-----
  |     |     |       |
  |     |     |       immutable borrow occurs here
  |     |     mutable borrow later used by call
  |     mutable borrow occurs here
  |
help: try adding a local storing this argument...
 --> src\main.rs:4:19
  |
4 |     ranks.swap(0, ranks.len() - 1);
  |                   ^^^^^^^^^^^
help: ...and then using that local as the argument to this call

By using this piece of advice the compiler gave me, I came up with following:

let last = ranks.len() - 1;
ranks.swap(0, last);

...which looks terrible.

So the thing is: why do I need to create local variable to store vector length, if I want to pass it to the method? Well, of course because of the borrowing rules: the value of ranks will be borrowed as mutable as I call swap, so that I can't use ranks.len() anymore.

But isn't it reasonable to suppose that the values of parameters will be calculated before the method starts to do anything and before it somehow can change the content of vector or it's length?

Basically, the order of computing parameters of a function is straight in Rust, which creates many obsticles for writing clean code for me. So I would like to ask, why the order is chosen to be straight? Cause if it was reverse, things would be much easier to express. For instance, the above piece of code could be rewritten as:

ranks.swap(0, ranks.len() - 1);

...which (I think you would agree) is much more readable and cleaner.

Also, it is strange that the similar in the sense of borrowing code compiles succesfully:

    let mut vec = vec![1, 2, 3]; // creating vector
    vec.push(*vec.last().unwrap()); // DOUBLE BORROWING HERE!!!
//   ^        ^
//   |        |
//   |        ----- immutable
//   ----- mutable
    println!("{:?}", vec);

So what the hell is going on? Double standarts, aren't they? I would like to know the answer. Thank you for explanation.

rustafari
  • 79
  • 4
  • @E_net4thecommentflagger note that while the questions are very similiar, this is not an exact duplicate; there the reason two-phase borrows is not applied is because `IndexMut` is special (in the bad sense) in this regard, and here it is because we have a deref in the middle. – Chayim Friedman Dec 22 '22 at 17:24

1 Answers1

1

but isn't it reasonable to suppose that the values of parameters will be calculated before the method starts to do anything and before it somehow can change the content of vector?

...are you sure?

Evaluation order of operands

The following list of expressions all evaluate their operands the same way, as described after the list. Other expressions either don't take operands or evaluate them conditionally as described on their respective pages.

  • ...
  • Call expression
  • Method call expression
  • ...

The operands of these expressions are evaluated prior to applying the effects of the expression. Expressions taking multiple operands are evaluated left to right as written in the source code.

So, self is evaluated before ranks.len() - 1. And self is actually &mut self because of auto-ref. So you first take a mutable reference to the vector, then call len() on it - that requires a borrowing it - while it is already borrowed mutably!

Now, this sometimes work (for example, if you would call push() instead of swap()). But this works by magic (called two-phase borrows that splits borrowing into borrowing and activating the reference), and this magic doesn't work here. I think the reason is that swap() is defined for slices and not Vec and thus we need to go through Deref - but I'm not totally sure.

Chayim Friedman
  • 47,971
  • 5
  • 48
  • 77