Rust:如果我知道一个可变借位只有一个示例,那么将它强制转换为一个指针并返回(以安抚借位检查器)安全吗?

cigdeys3  于 2023-01-17  发布在  其他
关注(0)|答案(1)|浏览(138)

我有一个使用四叉树的程序。这个树存储了对另一个容器(Vec)拥有的数据的可变借用。我在每个游戏循环中重建四叉树,但我不想重新分配,所以我clear了四叉树的底层Vec,而不是从头重建它。
下面是一个简单的例子,演示了同样的问题。这里我只是使用另一个Vec,而不是四叉树,因为它有同样的问题。

struct A;
fn main() {
    let mut owned_data = vec![A, A, A];
    let mut mut_borrowed_data = vec![];
    
    '_outer: loop {
        mut_borrowed_data.clear();
        '_inner: for borrow in &mut owned_data {
            mut_borrowed_data.push(borrow);
        }
    }
}

这会产生以下错误:

error[E0499]: cannot borrow `owned_data` as mutable more than once at a time
 --> src\main.rs:8:30
  |
8 |         '_inner: for borrow in &mut owned_data {
  |                              ^^^^^^^^^^^^^^^ `owned_data` was mutably borrowed here in the previous iteration of the loop

问题并不在于我在外层循环的前一次迭代中可变地借用,如果我删除它编译的mut_borrowed_data.push(data);,因为借用检查器意识到owned_data的可变借用在每个外层循环结束时被丢弃,因此可变借用的数量最大为1。该可变借用被“移动”到该容器中(如果我错了,请纠正我),因此它没有被删除,并且借位检查器不满意。如果我没有clear,将有可变借位的多个副本,借位检查器不够聪明,无法意识到我只推入mut_borrowed_data一次,而且每个外循环都推入clear.
但就目前情况而言,在任何时候都只有一个可变借位的示例,那么下面的代码安全吗?

struct A;
fn main() {
    let mut owned_data = vec![A, A, A];
    let mut mut_borrowed_data = vec![];
    
    '_outer: loop {
        mut_borrowed_data.clear();
        '_inner: for borrow in &mut owned_data {
            let ptr = borrow as *mut A;
            let new_borrow = unsafe { &mut *ptr };
            mut_borrowed_data.push(new_borrow);
        }
    }
}

现在编译。owned_data的可变借位(名为borrow)没有被移入mut_borrowed_data,因此它在外循环结束时被丢弃。这意味着owned_data只能被借用一次。不安全代码 * 获取指向数据的指针的副本,取消引用它,并创建指向该数据的新借用。(同样,如果我说错了,请纠正我)。因为这里使用的是复制而不是移动,所以编译器允许borrownew_borrow同时存在。这种unsafe的使用可能会违反借用规则,但是只要我在创建new_borrow之后不使用borrow,而且只要我清除mut_borrowed_data,那么我认为这是安全/健全的。
此外,(我认为)只要清除mut_borrowed_data vec
,借位检查器给出的保证仍然有效,它不会让我在一个循环中两次推入mut_borrowed_data,因为new_borrow在第一次插入后被移动。
我不想使用RefCell,因为我希望它尽可能地提高性能。四叉树的全部目的是提高性能,所以我希望尽可能地减少它引入的任何开销。增加借位计数可能是便宜的,但分支(检查该值是否〈= 1),数据的间接性和简单性的降低让我感到太多的不愉快。
我在这里使用unsafe安全吗?有没有什么东西会绊倒我?

dw1jzc5e

dw1jzc5e1#

让我们从这个开始:你的代码是安全的,而且相当可靠。
不安全代码获取指向数据的指针的副本,解引用它并创建指向该数据的新借位(如果我说错了,请再次纠正我)。因为这使用了副本而不是移动,所以编译器允许borrownew_borrow同时存在。
borrownew_borrow可以同时存在的原因并不是因为原始指针在引用移动时被复制,而是因为当你将引用转换为原始指针时,你分离了生存期链--编译器无法再跟踪new_borrow的源代码。
它不允许我在一个循环中两次推入mut_borrowed_data,因为new_borrow是在第一次插入之后移动的。
是,但也不是:

'_outer: loop {
    mut_borrowed_data.clear();
    '_inner: for borrow in &mut owned_data {
        let ptr = borrow as *mut A;
        let new_borrow = unsafe { &mut *ptr };
        mut_borrowed_data.push(new_borrow);
        mut_borrowed_data.push(new_borrow);
    }
}
// Does not compile:
// error[E0382]: borrow of moved value: `new_borrow`
//   --> src/lib.rs:12:32
//    |
// 10 |         let new_borrow = unsafe { &mut *ptr };
//    |             ---------- move occurs because `new_borrow` has type `&mut A`, which does not implement the `Copy` trait
// 11 |         mut_borrowed_data.push(new_borrow);
//    |                                ---------- value moved here
// 12 |         mut_borrowed_data.push(new_borrow);
//    |                                ^^^^^^^^^^ value borrowed here after move

// However, this does compile, and it is still Undefined Behavior:
'_outer: loop {
    mut_borrowed_data.clear();
    '_inner: for borrow in &mut owned_data {
        let ptr = borrow as *mut A;
        let new_borrow = unsafe { &mut *ptr };
        mut_borrowed_data.push(new_borrow);
        eprintln!("{borrow}"); // Use the old `borrow`.
    }
}

您可以通过隐藏原始的borrow使其更安全一些,这样它就不能再使用了:

'_outer: loop {
    mut_borrowed_data.clear();
    '_inner: for borrow in &mut owned_data {
        let borrow = unsafe { &mut *(borrow as *mut A) };
        mut_borrowed_data.push(borrow);
    }
}

但是它仍然不是完美的,原因是因为你分离了生存期,你得到了一个无限的,本质上是'static的引用,这意味着它可以使用比允许的更长的时间,例如:

use std::sync::Mutex;

#[derive(Debug)]
struct A;

static EVIL: Mutex<Option<&'static mut A>> = Mutex::new(None);

fn main() {
    let mut owned_data = vec![A, A, A];
    let mut mut_borrowed_data = vec![];
    
    '_outer: loop {
        if let Some(evil) = EVIL.lock().unwrap().as_deref_mut() {
            eprintln!("HaHa! We got two overlapping mutable references! {evil:?}");
        }
        
        mut_borrowed_data.clear();
        '_inner: for borrow in &mut owned_data {
            let borrow = unsafe { &mut *(borrow as *mut A) };
            mut_borrowed_data.push(borrow);
        }
        
        *EVIL.lock().unwrap() = mut_borrowed_data.pop();
    }
}

这并不意味着这种方法不好(这可能是我会使用的),但您需要小心。

相关问题