【问题标题】:How can I make `Box<dyn Iterator>` peekable and avoid lifetime errors?如何使 `Box<dyn Iterator>` 可窥视并避免生命周期错误?
【发布时间】:2023-01-09 03:08:05
【问题描述】:

我有以下类型定义:

pub struct UTF8Chars {
    bytes: Peekable<Box<dyn Iterator<Item = u8>>>,
}

现在我想知道如何实际创建这个结构的实例。

我试过了(是的,如果这是一个重要的细节,这是在特征实现中):

impl<'a> ToUTF8Chars for &'a str {
    fn utf8_chars(self) -> UTF8Chars {
        let bytes = Box::new(self.bytes()).peekable();

        UTF8Chars { bytes }
    }
}

这给了我错误:

expected struct `Peekable<Box<(dyn Iterator<Item = u8> + 'static)>>`
   found struct `Peekable<Box<std::str::Bytes<'_>>>`

Playground

如果我尝试了奇怪的事情,请原谅我,但我还没有掌握这种复杂的特质。据我所知,rust-analyzer 告诉我 Bytes 实际上是 impl Iterator&lt;Item = u8&gt;。所以,接下来我尝试的是先投射它:

let bytes = Box::new(self.bytes()) as Box<dyn Iterator<Item = u8>>;

UTF8Chars { bytes: bytes.peekable() }

那种工作,但现在借用检查员抱怨:

impl<'a> ToUTF8Chars for &'a str {
     -- lifetime `'a` defined here
     fn utf8_chars(self) -> UTF8Chars {
         let bytes = Box::new(self.bytes()) as Box<dyn Iterator<Item = u8>>;
                     ^^^^^^^^^^^^^^^^^^^^^^ cast requires that `'a` must outlive `'static`

我不确定这里有什么超出范围...据我所知,我拥有来自 .bytes() 的结果(我也尝试使用额外的 .clone() 以防假设不正确),我拥有BoxBox传递给Peekable,最后Peekable传递给UTF8Chars。什么确切地是这里的问题吗?为什么我需要比static...活得更久?

我发现这个问题看起来很相似,遗憾的是没有答案:Peekable of an Iterator in struct

我为什么要这样做?

好吧,主要是因为我不太关心,或者无法关心底层数据究竟是什么。我只需要知道我可以 .peek().next() 等等。这是因为有时我想将不同的东西分配给 self.bytes。例如,Chain&lt;...&gt;Copied&lt;...&gt; 而不是简单的 vec::IntoIter&lt;...&gt;

如果有替代方法,我很高兴听到它。

【问题讨论】:

    标签: rust


    【解决方案1】:

    所以,接下来我尝试的是先投射它:

    let bytes = Box::new(self.bytes()) as Box<dyn Iterator<Item = u8>>;
    

    在这种情况下这是正确的做法,尽管我会在 let 而不是 as 上使用类型注释来编写它。

    let bytes: Box<dyn Iterator<Item = u8>> = Box::new(self.bytes());
    

    特别是,必须有一个点,在该点缩小胁迫Box&lt;Bytes&gt;Box&lt;dyn Iterator&lt;Item = u8&gt;&gt; 发生了,那一点必须在Box 包含在其他东西之前(因为它实际上产生了一个不同的Box,一个添加了 vtable 指针)。

    在某些情况下,只需as _(未指定类型)就足以提示编译器不会立即断定该类型与传入类型相同。

    我不确定这里有什么超出范围......

    每个特征对象 (dyn) 类型都有生命周期,通常是隐式的。此生命周期指定该类型的实例保证多长时间有效——或者,从相反的角度来看,特征对象允许借用/包含哪些引用。

    当您不指定该生命周期时,特征对象位于 Boxlifetime elision rules make that lifetime be 'static 中。这就是您出现错误的原因:您试图将 Bytes&lt;'a&gt; 放在需要 'static 的地方。

    为了允许您的盒装迭代器借用,您必须定义类型和特征以具有生命周期。

    use core::iter::Peekable;
    
    pub struct UTF8Chars<'a> {
        bytes: Peekable<Box<dyn Iterator<Item = u8> + 'a>>,
    }
    
    trait ToUTF8Chars<'a> {
        fn utf8_chars(self) -> UTF8Chars<'a>;
    }
    
    impl<'a> ToUTF8Chars<'a> for &'a str {
        fn utf8_chars(self) -> UTF8Chars<'a> {
            let bytes: Box<dyn Iterator<Item = u8> + 'a> = Box::new(self.bytes());
    
            UTF8Chars {
                bytes: bytes.peekable(),
            }
        }
    }
    

    如果您不想添加生命周期,则只能使用拥有迭代器(例如String::into_bytes(s).into_iter())。在生命周期中,您可以使用拥有和借用迭代器。

    【讨论】:

    • 啊,当然,特质也有生命周期!我确实知道 + lifetime 的东西,但不知道这里需要它。是否只需要知道这一点,或者是否有某种方法可以从错误消息中获取该信息?
    • @Elias 没有太多好的线索,但是“cast requires that 'a must out live 'static”暗示有一个'static bound某处在你要投射的类型中。编译器不只是从无到有地组成'static 边界,所以此时要问的一个好问题是该边界从何而来。
    【解决方案2】:

    问题是 dyn Trait 类型默认情况下实际上是 dyn Trait + 'static,这意味着它们不允许借用任何数据。这对你来说是个问题,因为在 &amp;'a str 上调用 bytes() 返回的迭代器是从 str 借来的,因此不能比 'a 长寿。但是 'a 不会比 'static 长寿,所以你不能从中创建 dyn Iterator + 'static

    正如您可能已经猜到的,这里的解决方案是添加一些更通用的生命周期界限,首先是结构:

    pub struct UTF8Chars<'a> {
        //              ^^^^ now generic over 'a
        bytes: Peekable<Box<dyn Iterator<Item = u8> + 'a>>,
        //                  ------------------------^^^^
        // the iterator is now allowed to borrow data for 'a
    }
    

    然后是特征:

    trait ToUTF8Chars {
        fn utf8_chars<'a>(self) -> UTF8Chars<'a> where Self: 'a;
        //           ^^^^ also generic over 'a         ^^^^^^^^ self can borrow data for 'a
    }
    

    不过,根据您的具体用例,使用借来的接收器可能会更好:

    trait ToUTF8Chars {
        fn utf8_chars<'a>(&'a self) -> UTF8Chars<'a>;
        //                ^^^^ just borrow `self` for 'a
    }
    

    我敢肯定,在某些情况下,这两者并不相同(可能是对象安全?),但我无法确定具体情况。

    最后是实施:

    impl<'b> ToUTF8Chars for &'b str {
        fn utf8_chars<'a>(self) -> UTF8Chars<'a> where Self: 'a {
            let bytes = Box::new(self.bytes()) as Box<dyn Iterator<Item = u8>>;
    
            UTF8Chars { bytes: bytes.peekable() }
        }
    }
    

    或者

    impl ToUTF8Chars for str {
        fn utf8_chars<'a>(&'a str) -> UTF8Chars<'a> {
            let bytes = Box::new(self.bytes()) as Box<dyn Iterator<Item = u8>>;
    
            UTF8Chars { bytes: bytes.peekable() }
        }
    }
    

    ToUTF8Chars 的替代版本。

    【讨论】:

    • 感谢您的好回答,遗憾的是其他人的速度更快,所以我可能会接受他们的回答。不过,这个答案仍然包含可行的信息。我想我更喜欢这个特征声明:fn utf8_chars&lt;'a&gt;(self) -&gt; UTF8Chars&lt;'a&gt; where Self: 'a;,因为这样我就可以同时做impl ... for strimpl ... for &amp;str,不是吗?但无论如何我都不是专家。(您也可以回答我在其他答案下写的评论)
    • 对于 str&amp;str 的实施是正确的,但是如果不借用 str 就无法获得调用函数,所以这不会给你带来太多好处。至于你的其他问题,我想你可能只需要知道默认的'static绑定。 Trait 对象在历史上从来都不是 Rust 中记录最完备的部分,我无法在 The Book 或 nomicon 中快速找到该信息,尽管我确定它在某处。
    • @Elias 我最终在生命周期省略规则的参考中找到了它(当你考虑它时这是有道理的):doc.rust-lang.org/reference/…
    猜你喜欢
    • 1970-01-01
    • 2020-02-17
    • 1970-01-01
    • 2011-01-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-03-26
    • 2011-09-19
    相关资源
    最近更新 更多