首页 > 解决方案 > 使用产生 Stream 的异步 fn 的通用结构遇到问题

问题描述

从标题可以看出,这个例子中有很多概念发生冲突:

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=054bd4a02fe6eb72e50b6843ba04449d

我正在尝试做的事情:

我为使其发挥作用所做的努力令人沮丧。我认为那是因为我只有一个月的时间来学习 Rust,解决这个问题需要同时使用泛型、异步/等待、闭包和其他一些概念。我觉得我很接近,但我承认我很困。当前的错误与生命周期有关,我无法破译它:

error[E0495]: cannot infer an appropriate lifetime due to conflicting requirements

我有两个问题:

  1. 我会以正确的方式解决这个问题吗?这个例子在结构上有什么可以简化的吗?我正在从我想要使用此代码的方式向后工作(如在main操场的 fn 中:创建一个寻呼机,我需要一个来包装一个 fetch 函数)。
  2. 我怎样才能克服这个错误(以及接下来出现的任何错误)以使这个精简的示例正常工作?

感谢您阅读本文以及您可以提供的任何帮助!

标签: rust

解决方案


编译器试图(有点神秘地)告诉你,它不能确定实例在被调用Pager时不会被删除。self.fetch

实际上,由于您断言Stream应该具有'static生命周期,因此编译器正在尝试确保在此后的self.fetch 任何时间调用它都是安全的——这将包括在Pager 删除之后(无论何时)。但是一旦删除,self.fetch(可以找到指向 fetch 函数的指针的位置)将被释放,并且您正在尝试 use-after-free。

那么,解决方案是什么?有两种选择:

  1. 断言Pager实例的寿命超过Stream

    fn stream<'a>(&'a self) -> Pin<Box<dyn Stream<Item = T> + 'a>> { ... }
    

    如果您采用这种方法,那么您必须考虑从其环境中unfold捕获的闭包参数。&'a self默认情况下,它通过在闭包内部借用&'a self-leaving you with来做到这一点(外部借用存在于函数调用&&'a self的其余部分);stream再一次,这还不够长——你需要一个持续生命周期的借用Stream,它move可以通过将 移动&'a self到闭包中来给你:

    stream::unfold(0, move |state| ...);
    

    此外,async闭包中的块捕获其环境——再次默认借用,这与闭包存在相同的问题。所以你需要现在move&'a self闭包进入async块:

    async move { ... }
    

    (你已经在这样做了)。

    给我们整体:

    fn stream<'a>(&'a self) -> Pin<Box<dyn Stream<Item = T> + 'a>> {
        let stream = stream::unfold(0, move |state| async move {
            let yielded = (self.fetch)(state).await;
            let next_state = state + 1;
            Some((yielded, next_state))
        });
        Box::pin(stream)
    }
    

    在操场上看到它。

  2. 或者,由于self.fetch是一个函数指针而不是一个闭包(指针对象不捕获任何状态)并且unfold不从 捕获任何其他状态Pager,那么如果调用fetch时的值在unfold此后永远保持有效(即使Pager被删除),您可以简单地将函数指针复制出来,self以便可以将其移动到闭包中:

    fn stream(&self) -> Pin<Box<dyn Stream<Item = T>>> {
        let fetch = self.fetch;
        let stream = stream::unfold(0, move |state| async move {
            let yielded = fetch(state).await;
            let next_state = state + 1;
            Some((yielded, next_state))
        });
        Box::pin(stream)
    }
    

    (和以前一样,我们仍然需要move将此复制的函数指针放入闭包中,然后再放入async块中,因为仅仅借用局部fetch变量不会持续足够长的时间)。

    请注意,此方法要求将F类型参数限制为'static生命周期:

    F: Future<Output = T> + 'static,
    

    在操场上看到它。


推荐阅读