如何使用生锈中的闭包创建迭代器?

时间:2014-11-11 08:52:18

标签: rust

我天真地试图这样做:

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((&amp; mut self.state,))不会调用任务;它应该只在call()的持续时间内,据我所知,它应该是有效的。

所以这里的不匹配是在next()中的自我生命和'a a call}之间的生命...但我不明白为什么它不会'a。

修复上述代码的正确方法是什么?

有没有更好的方法来做到这一点?

围栏:http://is.gd/hyNi0S

3 个答案:

答案 0 :(得分:3)

这需要higher-rank lifetimes,因为闭包中的生命周期不应该是类型签名的一部分:闭包只是想要{em} 任何生命周期{{1} (因为它需要使用仅保证为&'a mut S方法的内部保留的数据来调用该函数:外部没有任何名称),而不是类型签名中外部暴露(并且可控制)的生命周期。这是不可能的,但我已经看到Niko Matsakis谈论在IRC上进行处理,并且有#18837之类的预备拉请求,所以希望很快就会出现。

要明确:代码失败,因为'a只能使用至少生成next的引用来调用,但next_只能使用'a。 },这不是&mut self.state,除非它被声明为&mut self(这就是编译器建议的原因)。添加此生命周期是非法的,因为它不满足特征声明的要求。

你现在可以使用旧的闭包解决这个问题(这基本上就是一个'a特征对象),甚至还有一个标准的库类型为你做这个:Unfold

答案 1 :(得分:3)

这是目前Rust的特质系统的一个不幸的限制,它将很快被解除。缺少higher-kinded lifetimes。据我所知,它的实施目前正在进行中。

让我们更接近检查您的结构定义(我已删除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引用,这显然不是你的情况 - self方法中next()的生命周期可能更短,所以你可以&#39;只是把它传递到关闭。只有在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特征的实现者(即你:)),所以它将是可以使用任何引用来调用next_,包括对Foo内部的引用。

答案 2 :(得分:0)

您可以使用两个通用特征对象:

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)()
    }
}

struct BoxedIterClosure<'a, T>(Box<FnMut() -> Option<T> + 'a>);

impl<'a, T> Iterator for BoxedIterClosure<'a, T>
{
    type Item = T;

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


fn main() {
    let mut it = (0..10).into_iter();
    let ic = IterClosure(|| it.next());

    println!("{}", ic.sum::<i32>());

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

    let mut it = (0..10).into_iter();
    let ic = BoxedIterClosure(Box::new(|| it.next()));

    println!("{}", ic.fold(0 as i32, |s, i| s+i));
}

您可以选择哪种实施符合更好的旅行要求。在BoxedIterClosure生命周期'a生命周期内,您只需借用闭包上下文而无需移动它。

我想提一下另一个SO答案,它解释了如何存储和调用回调。