I don't really understand why the borrow checker only allows one mutable reference at a time.
Doing research, the main example is that it breaks iterators like this, because the internal counter will be invalidated.
let mut elements = vec![1, 2, 3];
for v in elements {
if v % 2 == 0 { elements.push(0); }
}
But it feels more like a specific typing issue rather than a problem the borrow checker needs to fix.
If the iterator knows that the Vec, in this case is mutable, it should have to use a special iterator that can account for changes made in the loop, or it should just only accept non-mutable Vecs.
This is the only instance (I know of) of where multiple mutable references causes issues, but the solution of restricting mutable references to one can cause a lot of common borrow checker frustrations.
This is a simple example:
struct SubElement { value: i32 }
struct Example { elements: [SubElement; 2] }
impl Example {
fn modularize(&mut self, element: &mut SubElement) {
element.value = 100;
}
fn example(&mut self) {
// Raw code that I want to turn into a function.
self.elements[0].value = 100;
self.elements[1].value = 100;
// Turned into a function, has borrow checker errors, but would be safe.
self.modularize(&mut self.elements[0]);
self.modularize(&mut self.elements[1]);
}
}
It's difficult to functionalize certain operations that would be safe, when mutability is involved.
I might be wrong about this stuff, which is why I'm posting here.
struct SubElement { value: i32 }
struct Example { special_value: i32, elements: [SubElement; 2] }
impl Example {
fn modularize(&mut self, element: &SubElement) {
self.special_value = element.value;
}
fn example(&mut self) {
// Raw code that I want to turn into a function.
self.special_value = self.elements[0].value;
self.special_value = self.elements[1].value;
// Turned into a function, has borrow checker errors, but would be safe.
self.modularize(&self.elements[0]);
self.modularize(&self.elements[1]);
}
}
Thanks for the info.
That first proposal seems over-complicated to me, though I'm kind of new to the rust ecosystem.
My proposal is that perhaps this issue could be resolved with multiple mutable references, and putting the safety responsibility on the iterator functions themselves.
The rule that mutable references are never shared is one of the most essential features of the language and what famously differentiates Rust from other languages like C++.
It solves the common problem that some other part of the program might be changing something you're using, messing up your computation. Consider:
fn f() -> i32 {
let mut a = Some(5);
let b = &mut a;
let Some(five) = b else { return 0 };
// five refers to 5
*b = None;
*five // oops, the number 5 is no longer there
}
It prevents this code and all other similar cases from compiling.
This makes sense, though I could see there being a rule to prevent pattern matching on things that could still be mutated afterwards.
I appreciate this example though.
despite 'element' not being mutated. (same with add_twice)
Perhaps a keyword like 'will_mut' could be used.
I'm not really making a proposal to change such a fundamental part of rust, it's just a thought.
It would be nice if you could collapse a body of code into a function without worrying as much about the borrow rules, like you can in a functional language, for example.
You didn't answer the question whether my version would compile or not under your rules, without the will_mut. And if it doesn't compile, which line is wrong?
one optimization rust really wants to do "if I have a &x, and i read from it Y times I can read it into a register once, then use that register, no matter what the code between the usages is doing", as that would get rid of a load of memory loads and help further optimize code,
if you disallow having aliasing and mutability at the same time that's trivially sound, if you don't it's trivially unsound.
rust wants array reference iteration without (statically proven to be impossible) iterator invalidation (as that would be completely unsound),
with aliasing XOR mutability its somewhat trivial, without it its very, very difficult.
I would say yes, it would compile since it doesn't crash or do any undefined behaviour.
But if you wanted a more explicit and rust-like way you would have something like will_mut.
I've changed that rule, so you would need to use any amount of &mut, or &will_mut (in the case that you don't need to change that reference in your function).
I see now that you're taking an &mut, and turning it into a & reference, which is legal in rust and should be legal in my rules too. Not sure I can find a solution to this one that wouldn't involve some static analysis.