我有一个使用四叉树的程序.此树存储对另一个容器(VEC)拥有的数据的可变borrow .我重建四叉树每个游戏循环,但我不想重新分配,所以我clear的基础VEC的四叉树,而不是从头开始重建它.

下面显示了一个演示相同问题的简化示例.这里我使用的不是QuadTree,而是另一个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

问题其实并不在于我是在悄悄地borrow 外部循环的前一个迭代.如果我删除它编译的mut_borrowed_data.push(data);,因为借入判断器意识到owned_data的可变借入在每个外部循环的末尾被丢弃,因此可变借入的数量是最大值1.通过压入mut_borrowed_data,这个可变借入是moved进入这个容器(如果我错了请纠正我),因此它不会被丢弃并且借入判断器不高兴.如果我没有clear,就会有可变借入的多个副本,并且借入判断器不够聪明,无法意识到我只推入mut_borrowed_data一次,而我clear每个外循环都是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只是可变的借来一次.不安全代码takes a copy of the pointer to the data, dereferences it and creates a new borrow to that.(同样,如果我错了,请纠正我).因为这使用的是复制而不是移动,所以编译器允许borrownew_borrow同时存在.使用unSafe可能会违反borrow 规则,但只要我在创建new_borrow之后不使用borrow,并且只要我清除了mut_borrowed_data,那么我认为这是安全的.

此外,(我认为)借款判断员提供的担保仍然有效as long as I clear the mut_borrowed_data vec.它不会让我在一个循环中两次推入mut_borrowed_data,因为new_borrow在第一次插入后会移动.

我不想使用RefCell,因为我想让它尽可能地有性能.QuadTree的全部目的是提高性能,所以我想使它引入的任何开销都尽可能地精简.递增借入计数可能很便宜,但分支(判断该值是否为<=1)、间接性以及数据简单性的降低让我无法感到高兴.

我在这里使用不安全的词安全吗?有没有什么事会让我绊倒?

推荐答案

让我们从这一点开始:您的代码是安全的,而且非常可靠.

不安全代码获取指向数据的指针的副本,取消对它的引用,并创建对该数据的新borrow .(同样,如果我错了,请纠正我).因为这使用的是复制而不是移动,所以编译器允许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();
    }
}

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

Rust相关问答推荐

包含嵌套 struct 的CSV

如何将元素添加到向量并返回对该元素的引用?

如何在Rust中实现Functor trait?

如何格式化传入Rust中mysql crate的Pool::new的字符串

将数组转换为HashMap的更简单方法

捕获FnMut闭包的时间不够长

如何正确重新排列代码以绕过铁 rust 借入判断器?

允许 rust 迹 struct 条目具有多种类型

如何实现Deref;多次;?

Rust LinkedList 中的borrow 判断器错误的原因是什么?

可以在旋转循环中调用try_recv()吗?

OpenGL 如何同时渲染无纹理的四边形和有纹理的四边形

从 HashMap>, _> 中删除的生命周期问题

为什么 File::read_to_end 缓冲区容量越大越慢?

使用 rust 在 google cloud run (docker) 中访问环境变量的适当方法

如何创建递归borrow 其父/创建者的 struct ?

为什么在 rust 中删除 vec 之前应该删除元素

我如何将 google_gmail1::Gmail> 传递给线程生成?

如何用另一个变量向量置换 rust simd 向量?

为什么 u64::trailing_zeros() 在无分支工作时生成分支程序集?