引用生命周期与相同的生命周期范围冲突

3

我正在尝试在Rust中存储和使用可选的回调句柄,它类似于我正在存储的结构体的方法。只要我不将自身的引用传递给回调,它就可以工作。但是这样做会导致使用对象引用的生命周期错误(E0312)。生命周期似乎是一样的,我无法想出如何更改才能使其工作。

type Callback<'a> = Fn(&'a mut Func, i32) -> i32;

struct Func<'a> {
    val: i32,
    func: Option<Box<Callback<'a>>>,
}

impl<'a, 'b> Func<'b> {
    fn exec(&'a mut self, val: i32) -> i32 {
        if let Some(ref f) = self.func {
            return f(self, val);
        };
        0i32
    }
}

fn main() {
    let mut a32 = Func{
        val: 10i32,
        func: Some(Box::new(|ref mut s, val: i32| -> i32 {
            let v = s.val;
            s.val += 1;
            val * 32 + v
        }))
    };
    println!("a32(4) = {}", a32.exec(4i32));
    println!("a32(4) = {}", a32.exec(4i32));
}

有没有办法解决这个问题,还是我遇到了编译器的bug?
使用rustc 1.15.0(10893a9a3 2017-01-19)。
也可以在Rust playground上查看playground
我也尝试了不使用显式生命周期,但是我遇到了一个问题,即我不能在Rust中创建引用别名(E0502)。
我知道Rust试图防止数据竞争,但这是否意味着在这些情况下我总是需要创建对象的副本?
以下内容也不起作用,会给我一个错误,即借用的内容无法移动出去(E0507)。
impl Func {
    fn exec(&mut self, val: i32) -> i32 {
        if self.func.is_some() {
            return self.func.unwrap()(self, val);
        };
        0i32
    }
}

但是我找不到克隆封装函数的方法...

1个回答

3
这里存在一个借用问题:
1.您在不可变地借用self.func 2.您同时尝试可变地借用self
这是不允许的,因为它可能会在使用func时更改它,从而引发麻烦。
您可以尝试更改Callback以仅传递&mut i32,但是然后您将遇到生命周期统一问题:
1. 如果您指定exec采用&'a mut self,则锚定该对象并借用其余寿命, 2. 另一方面,如果您指定新的生命周期,则根据定义,它小于'a,并且您需要在Callback的签名中要求a。
两种情况都不起作用。
因此,解决方案是首先避免寿命。
(对于借用)只需传递对self.val的引用而不是self实例即可更容易地实现。
type Callback = Fn(&mut i32, i32) -> i32;

struct Func {
    val: i32,
    func: Option<Box<Callback>>,
}

impl Func {
    fn exec(&mut self, val: i32) -> i32 {
        if let Some(ref f) = self.func {
            return f(&mut self.val, val);
        };
        0i32
    }
}

fn main() {
    let mut a32 = Func{
        val: 10i32,
        func: Some(Box::new(|s: &mut i32, val: i32| -> i32 {
            let v = *s;
            *s += 1;
            val * 32 + v
        }))
    };
    println!("a32(4) = {}", a32.exec(4i32));
    println!("a32(4) = {}", a32.exec(4i32));
}

如果你想真正掌握Func,你需要进行"选项之舞":

impl Func {
    fn exec(&mut self, val: i32) -> i32 {
        let func = self.func.take();
        let res = if let Some(ref f) = func {
            f(self, val)
        } else {
            0i32
        };
        self.func = func;
        res
    }
}

请注意,在回调函数中 self.func 是空的。


我了解第二种方法,但我无法使其工作。感谢提供的示例。但简而言之,这意味着我不能存储方法函数回调,对吗?或者有一种方法可以获取复制/克隆的包装函数吗?实际上,我想要的是存储一个回调函数,当从方法中调用时,它会获取对象引用和传递的一些参数... - user2525536
将实例存储到自身中会形成循环引用,这是不允许的。而且这也是无意义的,因为你可以像我在最后一个示例中所做的那样,直接将&self / &mut self作为参数传递。但是请注意,今天你不能克隆任意闭包。它们不知道如何克隆自己。 - Matthieu M.
没有任何理由在 self 中存储 self 的实例,因为它仅作为参数传递,不存储在任何地方。我希望存储回调并使用对 self 实例的引用调用它,而不修改 self,但只要回调始终无法克隆并且重新借用不起作用,我将坚持提供的解决方案。 - user2525536
请注意,如果回调函数通过不可变引用获取了self,那么就可以避免这种“舞蹈”。否则的话,在回调函数中,您可能会在持有指向它的引用时覆盖self.func字段。这将是...糟糕的。另一个解决方案是将结构体分成两个部分:回调函数在其字段中,其他所有内容在另一个私有的“子结构”中。这样,您就可以像使用i32一样传递对子结构的可变引用。 - Matthieu M.
但这是否意味着我无法在exec中使用对self的_mutable_引用调用任何方法,因为这可能会使现有引用无效?因此,即使包装的函数对象是可克隆的,我仍然会陷入同样的问题... - user2525536
只要 f 被借用(在 if 块期间),就不行,在之前和之后可以。 - Matthieu M.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接