rust-lang / rust-lang/rfcs

Partial borrowing (for fun and profit)

Open
#1,215 73 comments 169 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

A-borrowck A-lifetimes A-syntax A-typesystem T-lang
Dominant language
Markdown
Stars
6.6k
Forks
1.7k
Avg merge
16h 14m
Merged PRs (30d)
1

Description

View all comments

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.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reading the partial-borrow examples in this issue, then review the linked Rust users discussion, rust-lang/rust issue #24187, and the OpenGL wrapper gist. Done would require a settled design for partial borrows and an accepted RFC direction; no implementation files or tests are identified here.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
compilers
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Needs clarification
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.