Yes indeed, it can look trivial from the outside, but hard to implement correctly and performantly. However, from a user/dev perspective I would expect foo(&mut x, x)
to work, particularly as the equivalent foo(x, &mut x)
works just fine. Can't say I am a compiler expert, but for that particular case of "taking a &mut
to pass as a function argument" it looks innocently enough to proof that the &mut
is not actually accessed before the function "starts" and thus other uses of &
to compute other function arguments can be allowed.
That being said, I still don't understand the following:
Why is v[v.len()-1]=5;
rejected, but v.push(v.len());
is accepted? Both look like identical cases of "syntactically &mut
is taken before a &
, but that &mut
isn't used just yet so it's ok". In particular as a "manual" implementation with traits is working fine. There is something specifically about operator[]
which messes this up. What is it, and what could be done to allow it?
Let me also add a real-world example where this is particularly annoying:
#[derive(Debug)]
struct Matrix {
elements: Vec<f32>,
rows: usize,
cols: usize,
}
impl Matrix {
pub fn set(&mut self, row: usize, col: usize, value: f32) {
self.elements[self.idx(row, col)] = value; // ERR
}
fn idx(&self, row: usize, col: usize) -> usize {
row * self.cols + col
}
}
fn main() {
let mut mat = Matrix { elements: vec![1.0,2.0,3.0,4.0,5.0,6.0], rows: 2, cols: 3 };
mat.set(1,2, 3.0);
println!("{:?}", mat);
}
One might say this case is even "worse" as the &mut
is applied blanket to the whole Self
object although the failing expression needs &mut
and &
for different fields.
And wrt to your example: I am not sure this should be rejected. It looks like another case of &mut
being created significantly before use. Maybe I have a wrong mental model, but I am thinking about &mut
as the "key which gives permission to write the value". However there is no reason to disallow other keys until that "write key" is actually used to write the value. The begin and end of the exclusiveness could be tailed to first and last use of the key, and at the very least in special cases like foo(&mut x, x)
. A related real-life example of this is instruction reordering which most compilers perform to improve performance.