Skip to content

Tracking issue for mutable_borrow_reservation_conflict compatibility lint #59159

Closed
@pnkfelix

Description

@pnkfelix

This is the summary issue for the mutable_borrow_reservation_conflict future-compatibility warning and other related errors. The goal of this page is describe why this change was made and how you can fix code that is affected by it. It also provides a place to ask questions or register a complaint if you feel the change should not be made. For more information on the policy around future-compatibility warnings, see our breaking change policy guidelines.

What is this lint about

A two-phase borrow is a mutable-borrow that is initially reserved at one point in the program's control-flow, and then subsequently activated at a later point in the control-flow.

For example, given a vector v, v.push(v.len()) first reserves a borrow of v when it evaluates the method receiver, but does not activate that borrow until later when transferring control to the push method itself, after v.len() has been evaluated.

This lint detects instances where the reservation itself conflicts with some pre-existing borrow. For example:

   let mut v = vec![0, 1, 2];
   let shared = &v;
             // ~~ 
             // a shared borrow of `v` starts here ...
   v.push(shared.len());
// ~      ~~~~~~
// |      ... and that shared borrow is used here...
// |
// ... but that comes after the reservation of the
//     mutable borrow of `v` here (the reservation
//     is subsequently activated when `push` is invoked)

The latter code is an example of code that was accepted when two-phased borrows (2PB) initially landed, as part of non-lexical lifetimes (NLL) deployment in the 2018 edition.

This lint detects such cases, and warns that this pattern may be rejected by a future version of the compiler.

This is much further discussion of this at the following places:

How to fix this warning/error

Revise the code so that the initial evaluation of the mutable borrow (the "reservation") always comes after all uses of shared borrows it conflicts with.

One example revision of the example above:

let mut v = vec![0, 1, 2];
let shared = &v;
let len = shared.len();
v.push(len);

Now, the last use of shared comes before the reservation in v.push(len), and thus there is no conflict between the shared borrow and the mutable borrow reservation.

Historical background

At the time NLL was stabilized, this borrowing pattern was not meant to be accepted, because it complicates the abstract model for borrows and thus poses problems for unsafe code authors and for future compiler optimizations. (How much complication it introduces is a matter of debate, which is in part why this restriction is being given treatment different from other future compatibility lints.)

In other words: at the time that NLL was stabilized, the compiler's acceptance of this borrowing pattern was categorized by the NLL team as a "known bug". The NLL team assumed that, as a bug fix, the compiler would be allowed to start rejecting the pattern in the future.

Whether a future version of the compiler rejects the code will depend on an investigation of potential abstract models of the language semantics; we will not convert the lint to a hard error without first performing an evaluation of such abstract models.

Timeline:

Current status

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-NLLArea: Non-lexical lifetimes (NLL)A-lintsArea: Lints (warnings about flaws in source code) such as unused_mut.C-future-incompatibilityCategory: Future-incompatibility lintsT-compilerRelevant to the compiler team, which will review and decide on the PR/issue.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions