串联的 &[u8] 切片如何在不进行额外复制的情况下实现 Read 特性?

How can concatenated &[u8] slices implement the Read trait without additional copying?

Read trait 是为 &[u8] 实施的。我怎样才能在几个串联的 u8 切片上获得 Read 特征而不首先实际进行任何串联?

如果我先连接,将有两个副本——将多个数组复制到一个数组中,然后通过 Read 特性从单个数组复制到目的地。我想避免第一次复制。

我想要 Read 优于 &[&[u8]] 的特性,将多个切片视为单个连续切片。

fn foo<R: std::io::Read + Send>(data: R) {
    // ...
}

let a: &[u8] = &[1, 2, 3, 4, 5];
let b: &[u8] = &[1, 2];
let c: &[&[u8]] = &[a, b];

foo(c); // <- this won't compile because `c` is not a slice of bytes.

围绕切片创建包装器类型并为其实现 Read。与 相比,我将切片委托给 Read 的实现:

use std::{io::Read, mem};

struct Wrapper<'a, 'b>(&'a mut [&'b [u8]]);

impl<'a, 'b> Read for Wrapper<'a, 'b> {
    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
        let slices = mem::take(&mut self.0);

        match slices {
            [head, ..] => {
                let n_bytes = head.read(buf)?;

                if head.is_empty() {
                    // Advance the child slice
                    self.0 = &mut slices[1..];
                } else {
                    // More to read, put back all the child slices
                    self.0 = slices;
                }

                Ok(n_bytes)
            }
            _ => Ok(0),
        }
    }
}

fn main() {
    let parts: &mut [&[u8]] = &mut [b"hello ", b"world"];
    let mut w = Wrapper(parts);

    let mut buf = Vec::new();
    w.read_to_end(&mut buf).unwrap();
    assert_eq!(b"hello world", &*buf);
}

更有效的实现将实现 Read 中的更多方法,例如 read_to_end or read_vectored

另请参阅:

  • How do I implement a trait I don't own for a type I don't own?

您可以使用 multi_reader crate,它可以连接任意数量的实现 Read:

的值
let a: &[u8] = &[1, 2, 3, 4, 5];
let b: &[u8] = &[1, 2];
let c: &[&[u8]] = &[a, b];

foo(multi_reader::MultiReader::new(c.iter().copied()));

如果你不想依赖外部 crate,你可以将切片包装在你自己的结构中并为其实现 Read:

struct MultiRead<'a> {
    sources: &'a [&'a [u8]],
    pos_in_current: usize,
}

impl<'a> MultiRead<'a> {
    fn new(sources: &'a [&'a [u8]]) -> MultiRead<'a> {
        MultiRead {
            sources,
            pos_in_current: 0,
        }
    }
}

impl Read for MultiRead<'_> {
    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
        let current = loop {
            if self.sources.is_empty() {
                return Ok(0); // EOF
            }
            let current = self.sources[0];
            if self.pos_in_current < current.len() {
                break current;
            }
            self.pos_in_current = 0;
            self.sources = &self.sources[1..];
        };
        let read_size = buf.len().min(current.len() - self.pos_in_current);
        buf[..read_size].copy_from_slice(&current[self.pos_in_current..][..read_size]);
        self.pos_in_current += read_size;
        Ok(read_size)
    }
}

Playground