rust-lang / rust-lang/rfcs

Conditionally-Revoked Borrows

Open
#881 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

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

Description

Rust's borrow checker, even with the SEME extension doesn't support the straight function with this signature:

pub fn cond_revoked_borrow
    <'a, A, B,
     F: for<'s> FnOnce(&'s mut A) -> &'s B,
     G: for<'s> FnOnce(&'s B) -> bool>
     (a: &'a mut A, op: F, cond: G)
     -> Result<&'a B, &'a mut A> {
    {
        let b = op(a);
        if cond(b) {
            return Ok(b)
        }
    }
    return Err(a)
}

A realistic specialization:

    {
        let buf = cursor.next();
        if is_valid(buf) {
            return Ok(buf)
        }
    }
    return Err(cursor)

In the standard erased-lifetime semantics, this function is safe, and you replace either of the returns with an infinite loop, it does: if the condition holds and b is returned, it is fine as a is borrowed for the entirity of 'a, and if it isn't, the scope of the borrow by op is limited to the block, and the Err return is fine.

However, if lifetimes aren't erased, then we encounter a problem - when we execute op, we must select a region parameter. Because cond could return false, the region may have to be at least 'a, but then if cond actually returns true, we need to "revoke" the region.

Should this pattern be legal? Should optimizations be allowed to assume it doesn't happen?

This isn't a totally non-conservative extension, because you can have this:

pub fn cond_revoked_borrow_acausal
    <'a, A, B,
     F: for<'s> FnOnce(&'s mut A) -> &'s B,
     G: for<'s> FnOnce(&'s B) -> bool>
     (a: &'a mut A, op: F, cond: G)
     -> Result<&'a B, &'a mut A> {
    if fortune_teller::cond_will_return_true()  {
        {
            let b = op(a);
            if cond(b) {
                return Ok(b)
            }
        }
        panic!("the fortune teller lied");
    } else {
        {
            let b = op(a);
            if cond(b) {
                panic!("the fortune teller lied");
            }
        }
        return Err(a)
    }
}

Which compiles, is consistent with the previous version up to panics (and I could've put a loop {} to make it consistent up to divergence), and works if you have a suitable implementation of fortune_teller, but this could create problems in the style of out-of-thin-air values.

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

The issue contains no file or test entry point; begin by analyzing the two Rust examples and the SEME versus erased-lifetime distinction described here. The work is complete when the project decides whether conditionally revoked borrows are legal and whether optimizations may assume they do not occur, with the decision recorded in the RFC process.

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
Stale
Clarity
Needs clarification
Newbie friendliness
20/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.