Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to force rust to drop a mutable borrow?

How can I force Rust borrow checker to forget about mutable borrow, with unsafe or not? I assumed that even lexical lifetimes should allow reusing mutable reference after a brackets block, but even Non-Lexical-Lifetimes from the fresh Rust release forbids that. With no outcome I tried some variants of unsafe and std::mem::drop.

#[derive(Debug)]
struct Storage {
    s: String,
    v: Vec<u8>,
}
#[derive(Debug)]
enum Ret<'a> {
    S(&'a mut String),
    V(&'a mut Storage),
}

fn fake_get_s<'a, 'b:'a>(_unused_storage: &'b mut Storage) -> Option<&'a mut String> {
    None
}

fn get_ret<'a> (storage: &'a mut Storage) -> Ret<'a> {
    // Borrowed for a limited block, assuming to release..
    {
        match fake_get_s(storage) {
            Some(string_ref) => {
                return Ret::S(string_ref);
            }
            None => (),
        }
        // ..with no one keeping the borrow..
    }
    // ..but, surprisingly, I still can't borrow the origin variable
    // Ridiculous, right?
    Ret::V(storage)
}

fn main() {
    let mut storage = Storage{s: "string".to_string(), v: vec![1, 2, 3]};
    let res = get_ret(&mut storage);
    println!("{:?}", res);
}

make sure this code does not compile

like image 205
makaleks Avatar asked Oct 25 '25 08:10

makaleks


1 Answers

Your code sadly runs into a known false positive in the borrow checker.

At the time of writing, there is no way to fix this issue without unsafe. Luckily, there's a workaround crate that encapsulates the unsafe in a sound manner, called polonius-the-crab.

Here it is, applied to your code:

use polonius_the_crab::{polonius, polonius_return};

#[derive(Debug)]
struct Storage {
    s: String,
    v: Vec<u8>,
}
#[derive(Debug)]
enum Ret<'a> {
    S(&'a mut String),
    V(&'a mut Storage),
}

fn fake_get_s<'a, 'b: 'a>(_unused_storage: &'b mut Storage) -> Option<&'a mut String> {
    None
}

fn get_ret<'a>(mut storage: &'a mut Storage) -> Ret<'a> {
    polonius!(|storage| -> Ret<'polonius> {
        match fake_get_s(storage) {
            Some(string_ref) => {
                polonius_return!(Ret::S(string_ref));
            }
            None => (),
        }
    });

    Ret::V(storage)
}

fn main() {
    let mut storage = Storage {
        s: "string".to_string(),
        v: vec![1, 2, 3],
    };
    let res = get_ret(&mut storage);
    println!("{:?}", res);
}

Note that storage now has to be a mut variable, because polonius borrows it indefinitely (like before), but then writes it back with the lifetime recovered through unsafe.


Alternatives

One solution I've used myself already is to decouple the decision from the result computation by querying twice:

fn get_ret(storage: &mut Storage) -> Ret<'_> {
    if fake_get_s(storage).is_some() {
        return Ret::S(fake_get_s(storage).unwrap());
    }

    Ret::V(storage)
}

Or, if you must overwrite the borrow checker with unsafe:

fn get_ret(storage: &mut Storage) -> Ret<'_> {
    {
        let storage = unsafe { &mut *(storage as *mut Storage) };

        match fake_get_s(storage) {
            Some(string_ref) => {
                return Ret::S(string_ref);
            }
            None => (),
        }
    }

    Ret::V(storage)
}
like image 197
Finomnis Avatar answered Oct 26 '25 23:10

Finomnis