Defination

Super borrows could only occurs in signatures, which:

  1. Prevent calling drop of the parameter (like normal borrows)
  2. May have an optional lifetime part, which cannot be automatically infered if exists.
  3. Might not be initialized (if we want something like placement new or super-let).

Example 1: get_or_insert

Currently, there is an interface, called get_or_insert, which might insert an item, thus an exclusive borrow &mut self is needed, and it is also get, thus a shared borrow &T is returned. Thus we finally got something like fn(&mut self)->&T. Since the &T is regarded as downgrading from &mut _, the existance of &T prevent other shared references. Even with polonius the error cannot be bypassed:

#![feature(hash_set_entry)]
fn main() {
    let mut set = std::collections::HashSet::from([1, 2, 3]);
    let ret = set.get_or_insert(100);
    let ret2 = set.get(&2);
    dbg!(ret2);
    dbg!(ret);
}
$ rustc --edition 2024 -Z unstable-options -Zpolonius -C link-arg=-fuse-ld=mold test.rs  -o test && ./test
error[E0502]: cannot borrow `set` as immutable because it is also borrowed as mutable
 --> test.rs:5:16
  |
4 |     let ret = set.get_or_insert(100);
  |               --- mutable borrow occurs here
5 |     let ret2 = set.get(&2);
  |                ^^^ immutable borrow occurs here
6 |     dbg!(ret2);
7 |     dbg!(ret);
  |          --- mutable borrow later used here

error: aborting due to 1 previous error

For more information about this error, try `rustc --explain E0502`.

Even if we could prove that, got another reference is safe, we just cannot make Rust know. In this case, I suppose my super borrow works:

fn get_or_insert<'a>(super 'a self, value: T) -> &'a T {
    let ret = self.base.get_or_insert(value); // currently ret is downgraded from &mut
    unsafe {
        // SAFETY: we have proven that, obtaining other shared borrow is safe here
        mem::transmute(ret)
    }
}

In signature, the signature suppose self is super-borrowed, which means it is exclusively borrowed at first, and finally the borrow become a shared borrow which lives 'a.

For a borrow checker, such function signature could be directly translated into 2 functions:

fn get_or_insert<'a>(super 'a self, value: T) -> &'a T {/*omit*/}
fn caller() {
//    let ret = self.get_or_insert(val);
    let _ = &mut self; // ensure something could be borrowed exslusively.
    let ret: &'a T = Self::get_or_insert(&'a self, val); // check whether we could borrow something for `&'a`.
}

For its body, just regard the super param as normal param without any borrow, and mark any borrow of super params, lives at least their super lifetime, might be enough.

Example 2: Placement new

Since we could send uninitialized variable into the stack, we could thus play some trick with placement new:

impl Foo {
    fn placement_new(super s: Self) {
        s = Self { ... }
    }
}
// call placement new:
let a: Foo;
Foo::placement_new(a);
// call placement new in depth:
fn caller() {
    let a: Foo;
    caller2(a); // we could transfer super parameter between functions
    // since caller2(a) does not drop `a`, we could use `a` later.
    // question: should we mark such `a` with a different symbol, e.g., `super a/new a/out a/...`?
}
fn caller2(super a: Foo) {
    Foo::placement_new(a);
}

Example 3: Control extern exclusive states

Suppose you have an FfiInterface, which might generate Unprotected values, and some call of FfiInterface might trigger FFI's GC, which recycle all unprotected things. Thus you must ensure every Unprotected is either dropped or protected before a FFI call.

A real world example is the famous statistic software, R. Currently, we have to protect all the variables in FFI calls, or we have to expose unsafe interface and manually check whether all FFI calls are valid. After super borrow implemented, we might have the capability to writting safe Rust.

pub struct Unprotected<'a>(*mut c_void, &'a ());
pub struct Protected(*mut c_void);
pub struct FfiInterface(()); // should not be constructed in safe Rust.
impl FfiInterface {
    unsafe fn new() -> Self { Self(()) } // construct a new interface is unsafe.
    // super 'a self means, an exclusive borrow is leaked, and it lives at least `'a`
    // note that, `allocate_in_ffi` may trigger GC, thus for stable Rust, it must be `&mut` to ensure no other valid Unprotected<'_> exists.
    // and thus it became another `fn (&mut self) -> &T`.
    pub fn allocate_in_ffi(super 'a self) -> Unprotected<'a> {
        unsafe { ffi_allocate() }
    }
}
impl Unprotected<'a> {
    pub fn protect(self) -> Protected {
        unsafe { ffi_protect(self.0) } // consume 'a, thus release the shared borrow of `FfiInterface`.
    }
}

Example 4: Writer example for super let

fn super_let(super 'a param) -> Writer {
    println!("opening file...");
    let filename = "hello.txt";
    file = File::create(filename).unwrap(); // param is initialized here.
    Writer::new(&file) // since file is not drop, this line is OK.
}

Example 5: Pin function for super let

Since make macros into function may need extra care, here I cannot avoid introduce a new grammar for optional parameters:

impl<'a, T> Pin<'a, T> {
    pub unsafe fn new_with_super_borrow(val: T; super 'a mut value: T) -> Self {
                                           // ^ note that semicolon, everything after this semicolon could be optional.
        value = val; // thus val is masked.
        unsafe { Pin::unchecked(&mut value) }
    }
}
let thing = Pin::new_with_super_borrow(Thing { … });

Further discussion: Default values

I have no idea whether allow default values is a good idea, but super value seems 100% suitable for default parameters, but that might make this discussion too large.

Alternatives:

  1. A better borrow checker?
  2. A more flexable borrow rule, which allow return mut borrow back and reborrow it as shared borrow, rather than just downgrade mut borrow, makes the shared borrow cannot be shared with other borrows.
  3. A better allocator that could ensure placement new is optimized?
  4. A better FFI interface which could track the status of some locks

1 post - 1 participant

Read full topic

Source: View source