如何在Rust中使用闭包创建迭代器?

6

我天真地尝试做了这件事:

struct Foo<'a, S: Send, T:Send> {
    next_:Box<Fn<(&'a mut S,), Option<T>> + Send>,
    state:S
}

impl<'a, S: Send, T: Send> Iterator<T> for Foo<'a, S, T> {
    fn next(&mut self) -> Option<T> {
        return self.next_.call((&mut self.state,));
    }
}

为了创建一个迭代器,我可以使用闭包轻松地将其发送到任务中。但是,这会生成可怕的生命周期不匹配错误:
<anon>:8:33: 8:48 error: cannot infer an appropriate lifetime for borrow expression due to conflicting requirements
<anon>:8         return self.next_.call((&mut self.state,));
                                         ^~~~~~~~~~~~~~~
<anon>:7:5: 9:6 help: consider using an explicit lifetime parameter as shown: fn next(&'a mut self) -> Option<T>
<anon>:7     fn next(&mut self) -> Option<T> {
<anon>:8         return self.next_.call((&mut self.state,));
<anon>:9     }
error: aborting due to previous error
playpen: application terminated with error code 101

我不理解这个错误。

闭包应该带有一个生命周期为'a的参数,这是结构体的生命周期。

状态由结构体拥有,因此它具有'a的生命周期。

使用next_.call((&mut self.state,))不会调用任务;它只应在调用()期间存在,据我所知,这应该是有效的。

因此,在next()中self的生命周期和call中的'a之间存在不匹配...但我不明白为什么它不是'a。

如何正确修复上面的代码?

是否有更好的方法来完成这个任务?

playpen: http://is.gd/hyNi0S

3个回答

3
这是当前Rust特质系统的一个不幸限制,但很快就会被解除。它缺少高阶生命周期。据我所知,目前正在进行其实现。
让我们更仔细地检查一下您的结构定义(我已经删除了mut以允许进一步推理使用'static,这并不会使它变得不够通用)。
struct Foo<'a, S: Send, T:Send> {
    next_: Box<Fn<(&'a S,), Option<T>> + Send>,  // '
    state: S
}

"'a生命周期参数在这里是一个输入参数。这意味着它由结构的用户提供,而不是由其实现者提供。

(顺便说一句,这并不是结构实例的生命周期 - 您无法仅使用类型参数指定这样的生命周期;它必须是对self引用的生命周期,但您也无法使用它,因为Iterator特质方法没有生命周期参数。然而,这只是一个旁注,并与实际问题无关)

这意味着您结构的用户可以任意选择'a,包括选择某个比结构的生命周期更长的生命周期,例如'static。现在观察这种选择如何转换结构(只需将'static替换为'a):

"
struct FooStatic<S: Send, T: Send> {
    next_: Box<Fn<(&'static S,), Option<T>> + Send>,  // '
    state: S
}

突然间,闭包只能接受 'static 引用,显然这不是你的情况——next() 方法中 self 的生命周期可能更短,所以你不能仅仅将其传递给闭包。只有当 self 的生命周期与 'a 相对应时(正如编译器所建议的那样),才能实现这一点:

fn next(&'a mut self) -> Option<T>

然而,正如我之前所说,你无法编写此代码,因为这会违反特性契约。使用高级生命周期,将有可能在闭包本身上指定生命周期参数。
struct Foo<S: Send, T: Send> {
    next_: Box<for<'a> Fn<(&'a mut S,), Option<T>> + Send>,
    state: S
}

这样做的好处是使闭包的生命周期由闭包的调用方(在本例中为实现 Iterator trait 的实现者)确定。因此,可以使用任何引用(包括指向 Foo 内部的引用)调用 next_


3
这需要使用higher-rank lifetimes,因为闭包中的生命周期不应该是类型签名的一部分:闭包只想要使用任何生命周期' a&'a mut S(因为它需要调用仅保证在next方法内部持续的数据,没有外部可命名的内容),而不是类型签名中公开(并且有些可控)的生命周期。这是不可能的,但我见过 Niko Matsakis 在 IRC 上谈论过这个问题,并且有类似#18837的预备拉取请求,所以这将很快出现。

需要说明的是:代码失败是因为next_只能使用至少存活'a的引用进行调用,但&mut self.state仅在&mut self存在期间有效,除非它声明为&'a mut self(这就是编译器建议的原因)。添加此生命周期是不合法的,因为它不满足特质声明的要求。

现在可以使用旧的闭包来解决此问题(本质上这就是Fn特征对象),甚至有一个标准库类型可以为您完成此操作:Unfold


1
如果你需要的话,这里有一个Unfold的例子:http://www.reddit.com/r/rust/comments/2iunxo/yield_and_impl_iterator/cl5mdoh - ArtemGr
1
Rust 现在具有更高级别的 trait 约束。 是否有更新答案的机会? - Brendan

0

你可以使用 itertools 中的 iterate,它恰好可以满足你的需求。

否则,如果你想在不使用状态概念的情况下将所有逻辑封装在闭包中,可以通过以下方式实现:

struct IterClosure<T, C>(C) where C: FnMut() -> Option<T>;

impl<T, C> Iterator for IterClosure<T, C> where C: FnMut() -> Option<T>
{
    type Item = T;

    fn next(&mut self) -> Option<Self::Item> {
        (self.0)()
    }
}

fn iter<T>(f: impl FnMut() -> Option<T>) -> impl Iterator<Item=T> {
    IterClosure(f)
}

fn main() {
    let mut it = (0..10).into_iter();
    let mut closure = || it.next();
    println!("{}", iter(closure).sum::<i32>());

    let mut it = (0..10).into_iter();
    iter(|| it.next()).for_each(
        |i| println!("{}", i)
    )
}

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