rust 如何使'Box〈dyn Iterator>'可查看并避免生存期错误?

c3frrgcw  于 2023-01-09  发布在  其他
关注(0)|答案(2)|浏览(124)

我有下面的类型定义:

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

现在我想知道如何实际创建这个结构体的示例。
我试过 (是的,如果这是一个重要的细节,这是在trait实现中)

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<'_>>>`
  • 一个月一次 *

请原谅我尝试了一些奇怪的东西,但我还没有掌握这种复杂的特性的窍门。据我所知,rust-analyzer告诉我Bytes实际上是impl Iterator<Item = u8>。所以,我接下来尝试的是先铸造它:

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<...>,或者Copied<...>,而不是简单的vec::IntoIter<...>
如果有其他的方法,我很乐意听到。

ztigrdn8

ztigrdn81#

所以,接下来我试着先选角:

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<Bytes>Box<dyn Iterator<Item = u8>>的 *unsizing concression * 发生,并且该点必须在Box包含在其他内容之前(因为它实际上产生了一个不同的Box,一个添加了vtable指针的Box)。
在某些情况下,仅as _(未指定类型)就足以提示编译器不立即得出该类型与传入类型相同的结论。
我不太确定什么超出了范围...
每一个trait对象(dyn)类型都有一个生命期,通常是隐式的,这个生命期指定了该类型的示例被保证有效的时间长度--或者,从相反的Angular 来看,trait对象被允许借用/包含什么引用。
当你没有指定那个生存期,trait对象在Box中,生存期省略规则会使那个生存期成为'static,这就是为什么你会有一个错误:你试图把一个Bytes<'a>放到一个需要'static的地方。
为了允许你的装箱迭代器借用,你必须定义类型和traits有一个生存期。

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())。有了生存期,你可以同时使用拥有迭代器和借用迭代器。

2o7dmzc5

2o7dmzc52#

问题是dyn Trait类型实际上默认为dyn Trait + 'static,这意味着它们不允许借用任何数据。这对您来说是个问题,因为通过在&'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的替代版本。

相关问题