如何解决如何返回一个从临时数组添加数据的链式迭代器?
我正在编写一个 MQTT5 库。要发送数据包,我需要在写入有效负载之前知道有效负载的大小。我用于确定大小的解决方案按重要性具有以下约束顺序:
- 易于维护
- 不应创建数据副本
- 应该具有相当的性能(避免重复计算)
要确定尺寸,我可以执行以下任何一种解决方案:
- 手工计算,这很烦人
- 在内存中保存要发送的数据副本,我想避免这种情况
- 为由
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(())
}
}
另见:
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。