3 ms·
IMO, it is reasonable that in the example given: struct Point { x: f64, y: f64, } impl Point { fn x_mut(&mut self) -> &mut f64 {
by School-Cotton 1y ago
IMO, it is reasonable that in the example given:
struct Point {
x: f64,
y: f64,
}
impl Point {
fn x_mut(&mut self) -> &mut f64 {
&mut self.x
}
fn y_mut(&mut self) -> &mut f64 {
&mut self.y
}
}
the returned references are, for the purposes of aliasing rules, references to the entire struct rather than to pieces of it. `x` and `y` are implementation details of the struct and not part of its public API. Yes, this is occasionally annoying but I think the inverse (the borrow checker looking into the implementations of functions, rather than their signature, and reasoning about private API details) would be more confusing.
I also disagree with the author that his rejected code:
fn main() {
let mut point = Point { x: 1.0, y: 2.0 };
let x_ref = point.x_mut();
let y_ref = point.y_mut();
*x_ref *= 2.0;
*y_ref *= 2.0;
}
"doesn't even violate the spirit of Rust's ownership rules."
I think the spirit of Rust's ownership rules is quite clear that when calling a function whose signature is
fn f<'a>(param: &'a mut T1) -> &'a mut T2;
`param` is "locked" (i.e., no other references to it may exist) for the lifetime of the return value. This is clear once you start to think of Rust borrow-checking as compile-time reader-writer locks.
This is often necessary for correctness (because there are many scenarios where you need to be guaranteed exclusive access to an object beyond just wanting to satisfy the LLVM "noalias" rules) and is not just an implementation detail: the language would be fundamentally different if instead the borrow checker tried to loosen this requirement as much as it could while still respecting the aliasing rules at a per-field level.
- jrpelkonen 1y agoI found the arguments in this article disingenuous. First, the author complains that borrowchecker examples are toys, then proceeds to support their case with rather contrived examples themselves. For instance, the map example is not using the entry api. They’d be better served by offering up some real world examples.
- timmytokyo 1y agoThe author explained why he used contrived examples. It's because the pain arises most acutely only after your project has become large and mature but demands a small ownership-impacting change. The toy examples demonstrate the problem in the small, but they generalize to larger and more complex scenarios. He's basically talking about the rigidity that Rust's borrow checking imposes on a program's data design. Once you've got the program following all the rules, it can be extraordinarily difficult to make even a minor change without incurring a time-consuming and painful refactor. This is an argument about the language's ergonomics, so it seems like a fair criticism.
- lblume 1y agoIt would not just be "confusing". It would be fundamentally unacceptable because there would just be no local reasoning anymore, and a single private field change might trigger a whole cascade of nonlocal borrowing errors. Unfortunately, this behavior does sometimes occur with Send bounds in deeply nested async code, which is why I mostly restrain from using colored-function style asynchronous code at all in favor of explicit threadpool management which the borrow checker excels at compared to every other language I used.
- Thiez 1y agoYou can also do something like: fn xy_mut(&mut self) -> (&mut f64, &mut f64) { let &mut Point { ref mut x, ref mut y } = self; (x,y) } The fact of the matter is that doing mutable getters in Rust is a bit inconvenient at times. There are various workarounds.
- whytevuhuni 1y agoYou can do that with less syntax, this works just fine: fn xy_mut(&mut self) -> (&mut f64, &mut f64) { let Point { x, y } = self; (x, y) } Also, I assume this is just a super trivial example, because in this case I'd just make x and y public. After all, you're giving public access via these methods, and the caller can do anything it wants with them, so there's no real difference. And if the fields are public, it's much easier to take mutable access of multiple fields separately.