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
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.
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)
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With