如何返回一个从临时数组添加数据的链式迭代器?

问题描述

我正在编写一个 MQTT5 库。要发送数据包,我需要在写入有效负载之前知道有效负载的大小。我用于确定大小的解决方案按重要性具有以下约束顺序:

  1. 易于维护
  2. 不应创建数据副本
  3. 应该具有相当的性能(避免重复计算)

要确定尺寸,我可以执行以下任何一种解决方案:

  1. 手工计算,这很烦人
  2. 在内存中保存要发送的数据副本,我想避免这种情况
  3. 为由 std::iter::ExactSizeIterator 本身组成的有效负载构建一个 std::iter::Chain,如果您不创建包装类型,这会导致快速的难看类型

我决定使用第 3 版。

下面的示例展示了我尝试编写 MQTT 字符串迭代器的过程。一个 MQTT 字符串由两个字节组成,即字符串的长度和 utf8 形式的数据。

use std::iter::*;
use std::slice::Iter;

pub struct MQTTString<'a> {
    chain: Chain<Iter<'a,u8>,Iter<'a,u8>>,}

impl<'a> MQTTString<'a> {
    pub fn new(s: &'a str) -> Self {
        let u16_len = s.len() as u16;
        let len_bytes = u16_len.to_be_bytes();
        let len_iter = len_bytes.iter(); // len_bytes is borrowed here

        let s_bytes = s.as_bytes();
        let s_iter = s_bytes.iter();

        let chain = len_iter.chain(s_iter);

        MQTTString { chain }
    }
}

impl<'a> Iterator for MQTTString<'a> {
    type Item = &'a u8;
    fn next(&mut self) -> Option<&'a u8> {
        self.chain.next()
    }
}

impl<'a> ExactSizeIterator for MQTTString<'a> {}

pub struct MQTTStringPait<'a> {
    chain: Chain<std::slice::Iter<'a,std::slice::Iter<'a,}

这个实现不能编译,因为我借用了 len_bytes 而不是移动它,所以它会在 Chain 可以使用它之前被删除

error[E0515]: cannot return value referencing local variable `len_bytes`
  --> src/lib.rs:19:9
   |
12 |         let len_iter = len_bytes.iter(); // len_bytes is borrowed here
   |                        --------- `len_bytes` is borrowed here
...
19 |         MQTTString { chain }
   |         ^^^^^^^^^^^^^^^^^^^^ returns a value referencing data owned by the current function

有什么好的方法可以做到这一点吗?将 len_bytes 添加MQTTString 结构没有帮助。有没有更好的第四种选择来解决这个问题?

解决方法

根本问题是 iter 借用了数组。在夜间 Rust 中,您可以使用 array::IntoIter,但它确实要求您更改迭代器以返回 u8 而不是 &u8

#![feature(array_value_iter)]

use std::array::IntoIter;
use std::iter::*;
use std::slice::Iter;

pub struct MQTTString<'a> {
    chain: Chain<IntoIter<u8,2_usize>,Copied<Iter<'a,u8>>>,}

impl<'a> MQTTString<'a> {
    pub fn new(s: &'a str) -> Self {
        let u16_len = s.len() as u16;
        let len_bytes = u16_len.to_be_bytes();
        let len_iter = std::array::IntoIter::new(len_bytes);

        let s_bytes = s.as_bytes();
        let s_iter = s_bytes.iter().copied();

        let chain = len_iter.chain(s_iter);

        MQTTString { chain }
    }
}

impl<'a> Iterator for MQTTString<'a> {
    type Item = u8;
    fn next(&mut self) -> Option<u8> {
        self.chain.next()
    }
}

impl<'a> ExactSizeIterator for MQTTString<'a> {}

您可以通过使用 Vec 在稳定的 Rust 中做同样的事情,但这有点矫枉过正。相反,由于您知道数组的确切大小,您可以获取值并链接更多:

use std::iter::{self,*};
use std::slice;

pub struct MQTTString<'a> {
    chain: Chain<Chain<Once<u8>,Once<u8>>,Copied<slice::Iter<'a,}

impl<'a> MQTTString<'a> {
    pub fn new(s: &'a str) -> Self {
        let u16_len = s.len() as u16;
        let [a,b] = u16_len.to_be_bytes();

        let s_bytes = s.as_bytes();
        let s_iter = s_bytes.iter().copied();

        let chain = iter::once(a).chain(iter::once(b)).chain(s_iter);

        MQTTString { chain }
    }
}

impl<'a> Iterator for MQTTString<'a> {
    type Item = u8;
    fn next(&mut self) -> Option<u8> {
        self.chain.next()
    }
}

impl<'a> ExactSizeIterator for MQTTString<'a> {}

另见:


从纯效率的角度来看,&u8 的迭代器不是一个好主意。在 64 位系统上,&u8 占用 64 位,而 u8 本身占用 8 位。此外,逐字节处理这些数据可能会妨碍围绕复制内存的常见优化。

相反,我建议创建一些可以将自身写入实现 Write 的内容。一种可能的实现:

use std::{
    convert::TryFrom,io::{self,Write},};

pub struct MQTTString<'a>(&'a str);

impl MQTTString<'_> {
    pub fn write_to(&self,mut w: impl Write) -> io::Result<()> {
        let len = u16::try_from(self.0.len()).expect("length exceeded 16-bit");
        let len = len.to_be_bytes();
        w.write_all(&len)?;
        w.write_all(self.0.as_bytes())?;
        Ok(())
    }
}

另见: