rust 为什么尽管使用了clone,但引用还是从lambda内部泄漏?

jckbn6z7  于 2022-11-12  发布在  其他
关注(0)|答案(2)|浏览(117)

我试图在一段使用回调的代码中实现clone_into_box模式,但遇到了一个我无法理解的错误。
基本上,我克隆了一个lambda的参数,但是借位检查器仍然抱怨我泄漏了对所述参数的引用。
我的问题:这是否是一个过度保守的借位检查器的例子(如果是这样,我如何以一种与借位检查器很好地配合的方式重写它),或者我在这里遗漏了什么,尽管克隆了,但确实泄漏了一个引用?
编码:

pub trait CloneIntoBox{
    fn clone_into_box<'a>(&self) -> Box<dyn CloneIntoBox + 'a>;
}

impl<'a> Clone for Box<dyn CloneIntoBox + 'a> {
    fn clone(&self) -> Self {
        self.as_ref().clone_into_box()
    }
}

# [derive(Clone)]

pub struct StructWithBox<'a> {
    pub my_box: Box<dyn CloneIntoBox + 'a>,
}

# [derive(Clone)]

struct StructThatCanBeClonedIntoBox {
    pub data: u32,
}

impl CloneIntoBox for StructThatCanBeClonedIntoBox {
    fn clone_into_box<'a>(&self) -> Box<dyn CloneIntoBox + 'a> {
        Box::new(self.clone())
    }
}

pub type WalkCallback<'a> = dyn FnMut(&StructWithBox) + 'a;

pub fn walk(data: Vec<u32>, cb: &mut WalkCallback) {
    for d in data{
        let instance = StructWithBox{my_box: Box::new(StructThatCanBeClonedIntoBox{data:d})};
        cb(&instance);
    }
}

fn main() {
    let data = vec![1, 2];
    let mut result = vec![];
    walk(data, &mut|param| result.push((*param).clone()));
}

Playground link(https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=80768178088472499d13667f577b5ec2)
提供:

error[E0521]: borrowed data escapes outside of closure
  --> src/main.rs:39:28
   |
38 |     let mut result = vec![];
   |         ---------- `result` declared here, outside of the closure body
39 |     walk(data, &mut|param| result.push((*param).clone()));
   |                     -----  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `param` escapes the closure body here
   |                     |
   |                     `param` is a reference that is only valid in the closure body
pxyaymoc

pxyaymoc1#

问题出在StructWithBox中的'a。请考虑以下walk()的实现:

impl<'b> CloneIntoBox for &'b u32 {
    fn clone_into_box<'a>(&self) -> Box<dyn CloneIntoBox + 'a> {
        Box::new(&42)
    }
}

pub fn walk(data: Vec<u32>, cb: &mut WalkCallback) {
    let data = 123u32;
    let instance = StructWithBox {
        my_box: Box::new(&data),
    };
    cb(&instance);
}

我还没有找到一种方法来使这实际上是不健全的:从impl CloneIntoBox for &u32返回的唯一内容是'static引用,因为'a是由调用者决定的。但是借位检查器并不知道:它假设'a可以是回调中的任何生存期,然后我们将它推送到回调外的向量中,但它可以在回调完成后被释放。
要解决此问题,可以在回调中使'a始终为'static

pub type WalkCallback<'a> = dyn FnMut(&StructWithBox<'static>) + 'a;

或者干脆摆脱终生:

pub trait CloneIntoBox {
    fn clone_into_box(&self) -> Box<dyn CloneIntoBox>;
}

impl Clone for Box<dyn CloneIntoBox> {
    fn clone(&self) -> Self {
        self.as_ref().clone_into_box()
    }
}

# [derive(Clone)]

pub struct StructWithBox {
    pub my_box: Box<dyn CloneIntoBox>,
}

# [derive(Clone)]

struct StructThatCanBeClonedIntoBox {
    pub data: u32,
}

impl CloneIntoBox for StructThatCanBeClonedIntoBox {
    fn clone_into_box(&self) -> Box<dyn CloneIntoBox> {
        Box::new(self.clone())
    }
}
xdnvmnnf

xdnvmnnf2#

pub type WalkCallback<'a> = dyn FnMut(&StructWithBox) + 'a;

等价于

pub type WalkCallback<'a> = dyn for<'b> FnMut(&StructWithBox<'b>) + 'a;

这要求回调函数能够接受任何生存期的StructWithBox,只要它在回调函数被调用时是活动的。由于StructWithBox的有效期可能不会超过这个时间,因此它不能被推送到生存期比回调函数更长的向量。
将此更改为

pub type WalkCallback<'a, 'b> = dyn FnMut(&StructWithBox<'b>) + 'a;

允许回调只接受长于'b的生存期。在这种情况下,'b被推断为与result的生存期一样长,因此可以将StructWithBox<'b>推送到result

相关问题