将内部引用传递给通用闭包时的关联类型生存期

时间:2018-10-28 15:57:19

标签: rust

这是一个可以很好编译的示例:

use std::cell::RefCell;
use std::rc::Rc;

struct Foo<'a> {
    val: Rc<RefCell<i32>>,
    dummy: Option<&'a i32>,
}

fn consume<T>(_: T) {}

impl<'a> Foo<'a> {
    // Note that &i32 has no lifetime markers
    fn subscribe<F>(self, func: F)
    where
        F: Fn(&i32) + 'a,
    {
        let val = self.val.clone();
        consume(move |x: i32| {
            *val.borrow_mut() = x;
            func(&*val.borrow())
        })
    }
}

这是我要实现但未编译的内容:

use std::cell::RefCell;
use std::rc::Rc;

trait Stream<'a> {
    type Item: 'a;

    fn subscribe<F>(self, func: F)
    where
        F: Fn(Self::Item) + 'a;
}

struct Bar<'a, S: Stream<'a>> {
    stream: S,
    val: Rc<RefCell<S::Item>>,
}

impl<'a, S: Stream<'a>> Stream<'a> for Bar<'a, S> {
    type Item = &'a S::Item; // 'a doesn't seem right here...

    fn subscribe<F>(self, func: F)
    where
        F: Fn(Self::Item) + 'a,
    {
        let val = self.val.clone();
        self.stream.subscribe(move |x: S::Item| {
            *val.borrow_mut() = x;
            func(&*val.borrow());
        })
    }
}

此示例与第一个示例几乎相同。唯一的区别是,因为它是一个特征,所以我们必须为关联类型Item分配一个显式生命周期,该类型是引用。将其设置为'a会导致生命周期冲突(理应如此):

error[E0495]: cannot infer an appropriate lifetime for lifetime parameter in function call due to conflicting requirements
  --> src/lib.rs:27:24
   |
27 |             func(&*val.borrow());
   |                        ^^^^^^
   |
note: first, the lifetime cannot outlive the lifetime  as defined on the body at 25:31...
  --> src/lib.rs:25:31
   |
25 |         self.stream.subscribe(move |x: S::Item| {
   |                               ^^^^^^^^^^^^^^^^^
note: ...so that closure can access `val`
  --> src/lib.rs:27:20
   |
27 |             func(&*val.borrow());
   |                    ^^^
note: but, the lifetime must be valid for the lifetime 'a as defined on the impl at 17:6...
  --> src/lib.rs:17:6
   |
17 | impl<'a, S: Stream<'a>> Stream<'a> for Bar<'a, S> {
   |      ^^
note: ...so that reference does not outlive borrowed content
  --> src/lib.rs:27:18
   |
27 |             func(&*val.borrow());
   |                  ^^^^^^^^^^^^^^

实际上,如果要将函数签名中的Fn(&i32)替换为Fn(&'a i32),可以将第一个示例修改为失败,并出现完全相同的错误。

是否可以使第二个示例编译?可能通过使用一些hack或不安全的块,我愿意真正接受任何东西。如果需要,可以更改签名或改组逻辑。关联类型Item的生存期应该是什么?

1 个答案:

答案 0 :(得分:0)

您可以通过指定F必须能够处理更通用的生命周期来编译第一个示例:

impl<'a> Foo<'a> {
    fn subscribe<F>(self, func: F)
    where
        for<'b> F: Fn(&'b i32) + 'a, // f can cope with any lifetime 'b
    {
        let val = self.val.clone();
        consume(move |x| {
            *val.borrow_mut() = x;
            func(&*val.borrow())
        })
    }
}

据我所知,您的第二个示例至少还遇到另一个问题:您致电

self.stream.subscribe(move |x: S::Item| {
    *val.borrow_mut() = x;
    func(&*val.borrow());
})

subscribe接受接受借用的函数(即&S::Item,而不是S::Item)。如果您传递参考,我不确定是否/如何将其分配给val.borrow_mut。您可能必须将其转换为拥有的价值。

正如您已经提到的,您也可以设置Item = S::Item(不借用)。但是,这意味着您不能简单地将val.borrow()传递给闭包内的func,因为这会从借入的值中转移出来。同样,解决方案可能是以某种方式将其转换为拥有的价值。