【问题标题】:Am I incorrectly implementing IntoIterator for a reference or is this a Rust bug that should be reported?我是否错误地实现了 IntoIterator 作为参考,或者这是一个应该报告的 Rust 错误?
【发布时间】:2017-04-01 18:33:05
【问题描述】:

进一步介绍了根据the Rust book 为包装向量实现IntoIterator 的示例,我还尝试根据the following code (Playground link) 实现 IntoIterator 以获取对包装器的引用: p>

struct VecWrapper(Vec<i32>);

impl VecWrapper {
    fn iter(&'static self) -> Iter {
        Iter(Box::new(self.0.iter()))
    }
}

struct Iter(Box<Iterator<Item = &'static i32>>);

impl Iterator for Iter {
    type Item = &'static i32;
    fn next(&mut self) -> Option<Self::Item> {
        self.0.next()
    }
}

impl IntoIterator for &'static VecWrapper {
    type Item = &'static i32;
    type IntoIter = Iter;
    fn into_iter(self) -> Self::IntoIter {
        self.iter()
    }
}

fn main() {
    // let test = vec![1, 2, 3]; // obviously, works
    let test = VecWrapper(vec![1, 2, 3]); // not working
    for v in &test {
        println!("{}", v);
    }
}

虽然实现编译,但在main 中使用它的尝试不会出现以下错误:

error[E0597]: `test` does not live long enough
  --> src/main.rs:31:14
   |
31 |     for v in &test {
   |              ^^^^^
   |              |
   |              borrowed value does not live long enough
   |              argument requires that `test` is borrowed for `'static`
...
34 | }
   | - `test` dropped here while still borrowed

与我实际想要使用的代码相比,此代码已大大简化为仅使用 'static 生命周期,使用现有的包含类型,并将 i32 用于内部(迭代)类型,但归结为只显示问题。


接受的答案解决了问题的第一部分,即不使用 'static 和使用带有特征的 + 'a。我仍然对实际代码有问题,这是一个LazyList 实现。我已将其发布为Am I incorrectly implementing IntoIterator for a reference to a LazyList implementation or is this a Rust bug?

【问题讨论】:

    标签: iterator rust lifetime borrow-checker


    【解决方案1】:

    您已经正确实现了对VecWrappers 的引用的迭代器,该迭代器在程序的整个长度内都存在——'static 的生命周期。

    您可能希望拥有一个通用的生命周期。然后将为每个实例提供一个具体的生命周期,该生命周期是唯一的。通常,我们是懒惰的,只是给这一生起名字'a

    struct VecWrapper(Vec<i32>);
    
    impl VecWrapper {
        fn iter(&self) -> Iter {
            Iter(Box::new(self.0.iter()))
        }
    }
    
    struct Iter<'a>(Box<dyn Iterator<Item = &'a i32> + 'a>);
    
    impl<'a> Iterator for Iter<'a> {
        type Item = &'a i32;
    
        fn next(&mut self) -> Option<Self::Item> {
            self.0.next()
        }
    }
    
    impl<'a> IntoIterator for &'a VecWrapper {
        type Item = &'a i32;
        type IntoIter = Iter<'a>;
    
        fn into_iter(self) -> Self::IntoIter {
            self.iter()
        }
    }
    
    fn main() {
        let test = VecWrapper(vec![1, 2, 3]);
        for v in &test {
            println!("{}", v);
        }
    }
    

    重要变化:

    • Box&lt;dyn Iterator&lt;Item = &amp;'a i32&gt; + 'a&gt; - + 'a 已添加。这是必需的,因为 trait 对象 将假定没有内部值引用任何生命周期较短的对象。
    • Item 类型现在是 &amp;'a i32
    • 通用生命周期在许多地方声明并在许多其他地方提供 (&lt;'a&gt;)。

    另见:


    通常,这里没有理由使用 trait 对象。我只是直接嵌入迭代器:

    struct Iter<'a>(std::slice::Iter<'a, i32>);
    

    这避免了任何间接的需要,在这种情况下无论如何都不会使用它。此外,它更明显地耦合了生命周期。

    【讨论】:

    • 我已经知道如何使用'a 而不是'static 来实现,正如我在问题中提到的只是简化并且已经在与您相同的地方使用'a 的版本;我错过的是Iter struct 定义中的+ 'a(你的第一点)。为此,我非常感谢你。对于阅读本文的其他人,正如@Shpmaster 所说,使用'static 在这里不起作用,即使使用+,因为迭代/for 循环中的一般用途将在另一个函数/块内。
    • 虽然我同意您的最后一个建议可能使代码在生命周期方面更清晰,但它需要对标准库的内部有深入了解,并增加了初始化的复杂性,例如 std::slice::Iter { ptr: self.0[0], end: (self.0[0] as *const i32).offset(self.0.len() as isize), _marker: std::marker::PhantomData },这是不可能的,因为ptrend_marker 字段是私有的,不能在模块外初始化这不可用,是吗?
    • @GordonBGood 需要深入了解标准库的内部结构——什么? Just... call the normal method.
    • 啊,对不起,我忘记了Vec 被重载调用来生成切片Iter;但是为了声明 Iter 包装器来包含它,它仍然需要我们知道 Vec::iter() 产生以及可以在哪个模块中找到它。
    • @GordonBGood 要求我们知道 Vec::iter() 产生 — 可在 API 文档和编译器中找到。它也是公共 API 的一部分,因此无法真正改变。
    猜你喜欢
    • 1970-01-01
    • 2014-11-22
    • 1970-01-01
    • 2023-03-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-04-06
    • 2021-12-28
    相关资源
    最近更新 更多