Description
Consider the following struct and impl for 2D coordinates:
struct Point {
x: f64,
y: f64
}
impl Point {
pub fn x_mut(&mut self) -> &mut f64 {
&mut self.x
}
pub fn y_mut(&mut self) -> &mut f64 {
&mut self.y
}
}
fn main() {
let mut point = Point { x: 1.0, y: 2.0 };
// ...
}
Even in this simple example, using point.x
and point.y
gives us more flexibility than using point.mut_x()
and point.mut_y()
. Compare following examples that double the components of a point:
{
// Legal:
point.x *= 2.0;
point.y *= 2.0;
}
{
// Legal:
let x_mut = &mut point.x;
let y_mut = &mut point.y;
*x_mut *= 2.0;
*y_mut *= 2.0;
}
{
// Legal:
let ref mut x_ref = point.x;
let ref mut y_ref = point.y;
*x_ref *= 2.0;
*y_ref *= 2.0;
}
{
// Legal:
*point.x_mut() *= 2.0;
*point.y_mut() *= 2.0;
}
{
// Illegal:
let x_mut = point.x_mut();
let y_mut = point.y_mut();
// ^~~~~
// error: cannot borrow `point` as mutable more than once at a time
*x_mut *= 2.0;
*y_mut *= 2.0;
}
The lifetime elision rules make it pretty clear why this happens: x_mut()
returns a mutable borrow that has to live at least as long as the mutable borrow of self
(written as fn x_mut<'a>(&'a self) -> &'a f64
).
In order to 'fix' the above example, there needs to be a way to say that a borrow only has to live as long as the mutable borrow of self.x
. Here's a modification to the above impl
block for a possible syntax to express partial borrows using a where
clause:
impl Point {
pub fn x_mut<'a>(&mut self)
-> &'a f64
where 'a: &mut self.x
{
&mut self.x
}
pub fn y_mut<'a>(&mut self)
-> &'a f64
where 'a: &mut self.y
{
&mut self.y
}
}
While the above examples aren't particularly encouraging, allowing for this type of change could be very powerful for abstracting away implementation details. Here's a slightly better use case that builds off of the same idea of representing a Point
:
struct Vec2(f64, f64) // Represents a vector with 2 coordinates
impl Vec2 {
// General vector operations
// ...
}
// Represents a coordinate on a 2D plane.
// Note that this change to `Point` would require no change to the above
// example that uses `Point::x_mut` and `Point::y_mut` (the details of
// the structure are effectively hidden from the client; construction could
// be moved into `Point::new` as well)
struct Point(Vec2);
impl Point {
pub fn x_mut<'a>(&mut self)
-> &'a mut f64
where 'a: &mut self.0.0
{
&mut self.0.0
}
pub fn y_mut<'a>(&mut self)
-> &'a mut f64
where 'a: &mut self.0.1
{
&mut self.0.1
}
}
For a more involved example of an API that requires mutable borrows, see this gist that describes a possible zero-cost OpenGL wrapper.
There has been some discussion about partial borrows in the past, but it hasn't really evolved into anything yet, and it seems like the idea hasn't been rejected either.