首页 文章

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

提问于
浏览
2

我天真地尝试这样做:

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

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

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

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

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

3 回答

  • 0

    这需要higher-rank lifetimes,因为闭包中的生命周期不应该是类型签名的一部分:闭包只是想在任何生命周期中使用 &'a mut S 'a (因为它需要调用具有仅保证持续内部的数据的函数) next 方法:外部无法命名),而不是类型签名中外部暴露(并且可控制)的生命周期 . 这并没有看到Niko Matsakis在IRC上谈论它的工作,并且有预备拉动请求,如#18837所以这有望很快出现 .

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

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

  • 3

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

    让's examine your struct definition closer (I' ve删除 mut 以允许使用 'static 进一步推理,这不会使它不那么通用):

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

    'a lifetime参数是此处的输入参数 . 这意味着它由结构的用户提供,而不是由其实现者提供 .

    (顺便说一句,它不是结构实例的生命周期 - 你不能只用类型参数指定这样的生命周期;它必须是 self 引用的生命周期,你也不能使用它,因为 Iterator trait方法没有生命周期参数 . 但这只是一个侧面说明,它与实际问题无关)

    这意味着您的结构的用户可以任意选择 'a ,包括选择一些大于结构生命周期的生命周期,例如 'static . 现在观察这种选择如何转换结构(只需用 'static 代替 'a ):

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

    突然之间,闭包只能接受 'static 引用,这显然不是你的情况 - next() 方法中 self 的生命周期可能会更短,所以你不能把它传递给闭包 . 这只能在 self lifetime确实对应 'a (由编译器建议)时才有效:

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

    但是,正如我之前所说,你不能写这个,因为它会违反特质 Contract .

    使用更高的生命周期,可以在闭包本身上指定生命周期参数:

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

    这样闭包的生命周期参数由闭包的调用者选择,在这种情况下,它是 Iterator trait的实现者(即你:)),因此可以用任何引用调用 next_ ,包括引用进入 Foo 内部 .

  • 3

    你可以通过使用两个通用特征对象来做到这一点:

    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 生命周期你可以借用闭包上下文而不移动它 .

    我想提一下另一个解释如何存储和调用回调的答案 .

相关问题