Rust in Everything
基于 bytes 1.12.1

适配器

主线位置:两条主线的外围。Chain、Take、Reader、IntoIter 包在 Buf 外面,读的通常是共享主线上的 Bytes(hyper 写 body 时把用户的 Bytes 包成 Take 和 Chain);Limit、Writer 和 Chain 的写方向包在 BufMut 外面,写的是构建主线上的 BytesMut 或 Vec<u8>。它们都不拥有缓冲,只改游标的算法。

flowchart TB
  buf["Buf"] --> chain["chain:Chain"]
  buf --> take["take:Take"]
  buf --> reader["reader:Reader"]
  buf --> iter["IntoIter"]
  bm["BufMut"] --> chainm["chain_mut:Chain"]
  bm --> limit["limit:Limit"]
  bm --> writer["writer:Writer"]
  reader --> ioread["std::io::Read 与 BufRead"]
  writer --> iowrite["std::io::Write"]

六个适配器都是「一个内层值加一两个字段」的小结构体,buf/ 下每个一个文件,合起来不到 800 行,大半是文档。它们的共同点:只改写自己语义要求改的那几个方法,其他方法(get_u16、put_u32 这些)全部继承 Buf / BufMut 的默认实现,而默认实现只调三个必需方法。所以读一个适配器,只要看它的三个必需方法加上它额外覆盖的一两个。

适配器怎么得到实现改写了什么
Chain<T, U>Buf::chain、BufMut::chain_mutBuf、BufMut、IntoIterator三个必需方法;Buf 方向另有 chunks_vectored、copy_to_bytes
Take<T>Buf::takeBuf三个必需方法、copy_to_bytes、chunks_vectored
Limit<T>BufMut::limitBufMut只有三个必需方法
Reader<B>Buf::reader(std)io::Read、io::BufRead—
Writer<B>BufMut::writer(std)io::Write—
IntoIter<T>Bytes、BytesMut、Chain 的 into_iter(),或 IntoIter::newIterator、ExactSizeIterator—

Reader、Writer 两个文件在 buf/mod.rs 里用 #[cfg(feature = "std")] 挡着:它们实现的是 std::io 的 trait,no_std 下没有。

Chain:两个 Buf 首尾相接

Chain 就是两个字段 a、b,先读完 a 再读 b。Buf 实现的三个必需方法加 chunks_vectored:

    fn remaining(&self) -> usize {
        self.a.remaining().saturating_add(self.b.remaining())
    }

    fn chunk(&self) -> &[u8] {
        if self.a.has_remaining() {
            self.a.chunk()
        } else {
            self.b.chunk()
        }
    }

    fn advance(&mut self, mut cnt: usize) {
        let a_rem = self.a.remaining();

        if a_rem != 0 {
            if a_rem >= cnt {
                self.a.advance(cnt);
                return;
            }

            // Consume what is left of a
            self.a.advance(a_rem);

            cnt -= a_rem;
        }

        self.b.advance(cnt);
    }

    #[cfg(feature = "std")]
    fn chunks_vectored<'a>(&'a self, dst: &mut [IoSlice<'a>]) -> usize {
        let mut n = self.a.chunks_vectored(dst);
        n += self.b.chunks_vectored(&mut dst[n..]);
        n
    }

Chain 不拷贝也不分配,两段各自保持原样。chunk 只能返回一个 &[u8],而两段在内存里不相邻,所以它只能给其中一段:a 还有数据就给 a.chunk(),否则给 b.chunk()。如果 a 本身也由几段组成(比如它又是一个 Chain),给的也只是 a 的第一段。advance 跨过边界时先把 a 读完,剩下的记到 b 上。chunks_vectored 是唯一能一次拿到两段的方法:先让 a 填,b 接着填剩下的位置。

hyper 写 chunked body 时,每个数据帧被包成 Chain<Chain<ChunkSize, B>, StaticBuf>(块头、数据、\r\n),再交给 writev,用户的 Bytes 不被拷贝,见 hyper 卷的 body 的解码与编码。

chunk 只给一段:一个跨段就坏的解析器

Buf::chunk 的约定(见 Buf)是「当前位置开始的一段」,可以比 remaining() 短。对 Bytes、&[u8] 这种连续的 Buf,chunk() 恰好就是全部剩余数据,写解析器时很容易把这当成规律。换成 Chain,数据被切在哪里,解析就在哪里出错。依赖:

bytes = "=1.12.1"
use std::io::IoSlice;
use std::panic;

use bytes::{Buf, Bytes};

// 错误写法:以为 chunk() 就是全部数据
fn find_crlf_buggy(buf: &impl Buf) -> Option<usize> {
  buf.chunk().windows(2).position(|w| w == b"\r\n")
}

// 错误写法:直接从 chunk() 里切 4 个字节当长度前缀
fn frame_len_buggy(buf: &impl Buf) -> u32 {
  u32::from_be_bytes(buf.chunk()[..4].try_into().unwrap())
}

fn main() {
  panic::set_hook(Box::new(|info| println!("panicked: {}", info.payload_as_str().unwrap_or("?"))));

  // 一行请求在两次 read 之间被切开
  let buf = Bytes::from_static(b"GET / HT").chain(Bytes::from_static(b"TP/1.1\r\nHost: a\r\n"));
  println!("remaining = {}, chunk = {:?}", buf.remaining(), Bytes::copy_from_slice(buf.chunk()));
  println!("find_crlf_buggy = {:?}", find_crlf_buggy(&buf));

  let mut slices = [IoSlice::new(&[]); 4];
  let n = buf.chunks_vectored(&mut slices);
  println!("chunks_vectored filled {n}: {:?}", slices[..n].iter().map(|s| s.len()).collect::<Vec<_>>());

  // 长度前缀被切在 2 + 2
  let mut frame = Bytes::from_static(&[0, 0]).chain(Bytes::from_static(&[0, 5, b'h', b'e', b'l', b'l', b'o']));
  let r = panic::catch_unwind(|| frame_len_buggy(&frame));
  println!("frame_len_buggy ok = {}", r.is_ok());
  println!("get_u32 = {}", frame.get_u32());
  println!("rest = {:?}", frame.copy_to_bytes(5));
}

M2 Max、rustc 1.99.0,--release 跑 3 次,输出相同:

remaining = 25, chunk = b"GET / HT"
find_crlf_buggy = None
chunks_vectored filled 2: [8, 17]
panicked: range end index 4 out of range for slice of length 2
frame_len_buggy ok = false
get_u32 = 5
rest = b"hello"
  • remaining() 是 25,完整的一行 GET / HTTP/1.1\r\n 就在里面,但 chunk() 只有前 8 个字节 GET / HT,find_crlf_buggy 返回 None。在一个「找不到行尾就等更多数据」的循环里,这意味着永远等下去:数据早就到了,只是不在第一段里。
  • frame_len_buggy 从 2 字节的第一段里切 4 个字节,标准库切片越界 panic:range end index 4 out of range for slice of length 2。
  • 同一个 frame 用 get_u32 读就是对的:get_* 先看当前 chunk 够不够,不够就退回逐段拷贝(快慢两条路径见 Buf)。
  • chunks_vectored 一次填了 2 个 IoSlice,长度 8 和 17,两段都在。

所以解析不定长的东西(找分隔符)时,要么像 hyper 那样先把数据读进同一块 BytesMut、保证连续,要么遍历 chunks_vectored 给出的每一段并处理跨段的匹配;读定长字段就用 get_* / try_get_* / copy_to_slice,它们本来就按段处理。VecDeque<u8> 的 Buf 实现也有同样的性质:环形缓冲绕回开头时 chunk 只给前一段。

copy_to_bytes:不跨边界时不拷贝

Chain::copy_to_bytes 分三种情况:

    fn copy_to_bytes(&mut self, len: usize) -> crate::Bytes {
        let a_rem = self.a.remaining();
        if a_rem >= len {
            self.a.copy_to_bytes(len)
        } else if a_rem == 0 {
            self.b.copy_to_bytes(len)
        } else {
            assert!(
                len - a_rem <= self.b.remaining(),
                "`len` greater than remaining"
            );
            let mut ret = crate::BytesMut::with_capacity(len);
            ret.put(&mut self.a);
            ret.put((&mut self.b).take(len - a_rem));
            ret.freeze()
        }
    }

要的字节全在一段里,就交给那一段自己的 copy_to_bytes;两段都是 Bytes 时这是 split_to,不拷贝(切片)。只有跨过边界时才分配一个 BytesMut,把两边各拷一部分进去,这时结果必须是连续的,没有别的办法。这个覆盖来自 PR #481(「Specialize copy_to_bytes for Chain and Take」,1.1.0),说明写得很直白:「Chain it works if the requested bytes does not cross boundary between Chain members.」用指针相等看一遍:

use bytes::{Buf, Bytes};

fn main() {
  let a = Bytes::from(b"hello ".to_vec());
  let b = Bytes::from(b"world".to_vec());
  let (pa, pb) = (a.as_ptr(), b.as_ptr());
  let mut c = a.chain(b);
  let x = c.copy_to_bytes(3); // 全在 a 里
  println!("within a: {:?} shares a: {}", x, x.as_ptr() == pa);
  let y = c.copy_to_bytes(5); // 跨过 a、b 的边界
  println!("across:   {:?} shares a: {} shares b: {}", y, y.as_ptr() == pa.wrapping_add(3), y.as_ptr() == pb);
  let z = c.copy_to_bytes(3); // a 已读完,全在 b 里
  println!("within b: {:?} shares b: {}", z, z.as_ptr() == pb.wrapping_add(2));
}

跑 3 次,输出相同:

within a: b"hel" shares a: true
across:   b"lo wo" shares a: false shares b: false
within b: b"rld" shares b: true

第一次和第三次的结果直接指在 a、b 原来的缓冲里(第三次是 b 的第 2 个字节,前两个被第二次读走了);第二次跨边界,是一块新分配。

BufMut 方向,以及两个 remaining 的加法

Chain 的 BufMut 实现和 Buf 方向逐行对称:chunk_mut 在 a.has_remaining_mut() 时给 a.chunk_mut(),否则给 b 的;advance_mut 先填满 a 再记到 b 上。典型用法是「先写进一块固定的数组,写满了溢出到一个 Vec」。两个方向的 remaining / remaining_mut 都用 saturating_add,而不是普通的加法。

📝NOTE
冷知识:Chain 的 remaining_mut 从「溢出就 panic」改成了饱和加法

最早是 self.a.remaining_mut() + self.b.remaining_mut()。PR #482(「Panic on integer overflow in Chain::remaining」,1.1.0,说明只有一句「Make it safer.」)把两个方向都改成 checked_add(..).unwrap()。

问题是会增长的缓冲把 remaining_mut 报得很大:Vec<u8> 是 isize::MAX - len,当时的 BytesMut 是 usize::MAX - len(BufMut 与 UninitSlice)。把一个定长数组和一个 Vec 接起来、再接一个,加法就溢出,checked_add(..).unwrap() 直接 panic。PR #488(「Fix chain remaining_mut(), allowing to chain growing buffer」,1.2.0)的作者就是这样踩到的:「Stumbled upon panic when was trying to chain fixed buffer with growing buffer.」它把 remaining_mut 改成 saturating_add,并给 trait 文档加了一条注:「remaining_mut may return value smaller than actual available space.」作者也写明了代价:饱和之后,写入前后 remaining_mut 可能都是 usize::MAX,看不出变化。新加的测试 chain_growing_buffer 把一个 10 字节数组、一个 Vec 和一个空 Vec 接起来写 15 个字节,注释是「Required for potential overflow because remaining_mut for Vec is isize::MAX - vec.len(), but for chain_mut is usize::MAX」。Buf 方向的 remaining 后来在 1.6.0 的一次清理(#635)里也从 checked_add(..).unwrap() 换成了 saturating_add。

Take:最多读 limit 个字节

Take 是 inner 加一个 limit,Buf 实现把每个方法都截到 limit 以内:

    fn remaining(&self) -> usize {
        cmp::min(self.inner.remaining(), self.limit)
    }

    fn chunk(&self) -> &[u8] {
        let bytes = self.inner.chunk();
        &bytes[..cmp::min(bytes.len(), self.limit)]
    }

    fn advance(&mut self, cnt: usize) {
        assert!(cnt <= self.limit);
        self.inner.advance(cnt);
        self.limit -= cnt;
    }

    fn copy_to_bytes(&mut self, len: usize) -> crate::Bytes {
        assert!(len <= self.remaining(), "`len` greater than remaining");

        let r = self.inner.copy_to_bytes(len);
        self.limit -= len;
        r
    }

advance 超过 limit 是一个不带信息的 assert!,panic 信息就是 assertion failed: cnt <= self.limit。copy_to_bytes 直接转给内层(同样来自 #481),所以 Bytes 外面套一层 Take 依然不拷贝。Take 读多少,内层就真的前进多少:into_inner 拿回内层时,被读掉的字节已经不在了,limit 之外的还在。hyper 用它表示「body 只写 Content-Length 那么多」(BufKind::Limited(Take<B>)),见 body 的解码与编码。

chunks_vectored 比较长:

    #[cfg(feature = "std")]
    fn chunks_vectored<'a>(&'a self, dst: &mut [IoSlice<'a>]) -> usize {
        if self.limit == 0 {
            return 0;
        }

        const LEN: usize = 16;
        let mut slices: [IoSlice<'a>; LEN] = [IoSlice::new(&[]); LEN];

        let cnt = self
            .inner
            .chunks_vectored(&mut slices[..dst.len().min(LEN)]);
        let mut limit = self.limit;
        for (i, (dst, slice)) in dst[..cnt].iter_mut().zip(slices.iter()).enumerate() {
            if let Some(buf) = slice.get(..limit) {
                // SAFETY: We could do this safely with `IoSlice::advance` if we had a larger MSRV.
                let buf = unsafe { std::mem::transmute::<&[u8], &'a [u8]>(buf) };
                *dst = IoSlice::new(buf);
                return i + 1;
            } else {
                // SAFETY: We could do this safely with `IoSlice::advance` if we had a larger MSRV.
                let buf = unsafe { std::mem::transmute::<&[u8], &'a [u8]>(slice) };
                *dst = IoSlice::new(buf);
                limit -= slice.len();
            }
        }
        cnt
    }

先让内层往一个栈上的临时数组里填(最多 16 个),再逐个抄到 dst,累计长度碰到 limit 的那一段截短后立即返回。IoSlice 本身不能截短,只能从 &[u8] 重新造一个;而 slice 借的是本函数里的临时数组,slice.get(..limit) 得到的引用生命周期被缩短到了这次借用,transmute 把它恢复成 'a。底层字节确实活 'a 那么久(它们属于 self),注释说明了只是 MSRV 不够、用不了 IoSlice::advance。

📝NOTE
冷知识:Take 曾经让 h2 的 vectored write 退化成一段一段写

Take 原先没有覆盖 chunks_vectored,用的是 Buf 的默认实现:只填一个 IoSlice,内容是 self.chunk()。PR #617(「add specialized Buf::chunks_vectored for Take」,1.10.0)的动机是:「The h2 crate uses Take around the data frames to enforce a maximum frame size, which breaks vectored writes since Take doesn't provide a specialized chunks_vectored that calls the underlying chunks_vectored.」内层是一个 Chain 时,包上 Take 之后 writev 每次只能拿到第一段。

作者提到另一种不用 unsafe 的做法:遇到跨过 limit 的那一段直接丢掉而不是截短。但那样可能违反当时 chunks_vectored 文档里的一条保证:「If chunk_vectored does not fill every entry in dst, then dst is guaranteed to contain all remaining slices in self」。所以选择了截短,代价就是上面那两处 transmute。其实就在 #617 合并前三天,PR #754(「Remove incorrect guarantee for chunks_vectored」)已经把那条保证删掉了,两者都在 1.10.0 发布。现在的文档只保证「If Buf::remaining() is non-zero, then this writes at least one non-empty slice to dst」,上面最多只取 16 段的写法也满足这一条。

Limit:最多写 limit 个字节

Limit 是 Take 在写方向上的镜像,BufMut 实现只有三个必需方法:

unsafe impl<T: BufMut> BufMut for Limit<T> {
    fn remaining_mut(&self) -> usize {
        cmp::min(self.inner.remaining_mut(), self.limit)
    }

    fn chunk_mut(&mut self) -> &mut UninitSlice {
        let bytes = self.inner.chunk_mut();
        let end = cmp::min(bytes.len(), self.limit);
        &mut bytes[..end]
    }

    unsafe fn advance_mut(&mut self, cnt: usize) {
        assert!(cnt <= self.limit);
        self.inner.advance_mut(cnt);
        self.limit -= cnt;
    }
}

chunk_mut 截短用的是 UninitSlice 的区间下标。它没有覆盖 put_slice,所以即使内层是 Vec<u8>,经过 Limit 写入也走默认的 chunk_mut / advance_mut 循环,而不是 extend_from_slice;默认 put_slice 开头那个 remaining_mut() < src.len() 的检查在这里第一次真正起作用。一个 Vec<u8> 被限制在 4 字节时,chunk_mut 仍会让内层 reserve(64):截短的只是交出去的那一段,不是内层的分配。这两点都在下面的动脑筋里。

❗IMPORTANT
动脑筋:Take 能交出几段,Limit 写超了是什么 panic?

平台 M2 Max、rustc 1.99.0,--release:

use std::io::IoSlice;
use std::panic;

use bytes::{Buf, BufMut, Bytes};

fn main() {
  panic::set_hook(Box::new(|info| println!("  panicked: {}", info.payload_as_str().unwrap_or("?"))));

  println!("== Take");
  let src = Bytes::from_static(b"abc").chain(Bytes::from_static(b"defgh"));
  let mut t = src.take(5);
  println!("  remaining {} chunk {:?}", t.remaining(), Bytes::copy_from_slice(t.chunk()));
  let mut slices = [IoSlice::new(&[]); 4];
  let n = t.chunks_vectored(&mut slices);
  println!("  chunks_vectored {n}: {:?}", slices[..n].iter().map(|s| s.len()).collect::<Vec<_>>());
  t.advance(4);
  println!("  after advance(4): remaining {} limit {} inner remaining {}", t.remaining(), t.limit(), t.get_ref().remaining());
  let r = panic::catch_unwind(panic::AssertUnwindSafe(|| t.advance(2)));
  println!("  advance(2) ok = {}", r.is_ok());

  println!("== Limit");
  let mut l = Vec::<u8>::new().limit(4);
  println!("  remaining_mut {}", l.remaining_mut());
  println!("  chunk_mut len {} (inner cap now {})", l.chunk_mut().len(), l.get_ref().capacity());
  let r = panic::catch_unwind(panic::AssertUnwindSafe(|| l.put_slice(b"12345")));
  println!("  put_slice(5 bytes) ok = {}", r.is_ok());
  l.put_slice(b"1234");
  println!("  after put_slice(4): {:?} remaining_mut {}", l.get_ref(), l.remaining_mut());
}

问:chunks_vectored 那一行打印几段、各多长?put_slice(b"12345") 的 panic 信息是哪一句?

Reader 与 Writer:接到 std::io

这两个是给只认 std::io::Read / Write 的代码用的桥,要 std feature。Reader 的全部实现:

impl<B: Buf + Sized> io::Read for Reader<B> {
    fn read(&mut self, dst: &mut [u8]) -> io::Result<usize> {
        let len = cmp::min(self.buf.remaining(), dst.len());

        Buf::copy_to_slice(&mut self.buf, &mut dst[0..len]);
        Ok(len)
    }
}

impl<B: Buf + Sized> io::BufRead for Reader<B> {
    fn fill_buf(&mut self) -> io::Result<&[u8]> {
        Ok(self.buf.chunk())
    }
    fn consume(&mut self, amt: usize) {
        self.buf.advance(amt)
    }
}

BufRead 和 Buf 几乎是同一个接口:fill_buf 就是 chunk,consume 就是 advance。所以 fill_buf 也只给一段,但 read_line 这类标准库方法本来就会反复 fill_buf / consume,跨段没有问题。read 从不失败,读完了返回 Ok(0)。

Writer:

impl<B: BufMut + Sized> io::Write for Writer<B> {
    fn write(&mut self, src: &[u8]) -> io::Result<usize> {
        let n = cmp::min(self.buf.remaining_mut(), src.len());

        self.buf.put_slice(&src[..n]);
        Ok(n)
    }

    fn flush(&mut self) -> io::Result<()> {
        Ok(())
    }
}

write 写不下就少写,返回实际写入的字节数,这是 io::Write 允许的「短写」;写满之后返回 Ok(0),write_all 会把它变成 WriteZero 错误。对 BytesMut 和 Vec<u8> 来说 remaining_mut 是 isize::MAX - len,实际上不会短写;套上 Limit 才会。1.9.0 之前这里调的是 self.buf.put(&src[0..n]),PR #745 改成 put_slice,理由是 put 「has to handle non-contiguous Bufs」,而 &[u8] 是连续的。

use std::io::{self, BufRead, Read, Write};

use bytes::{Buf, BufMut, Bytes, BytesMut};

fn main() {
  println!("== Writer");
  let mut w = BytesMut::new().limit(8).writer();
  let n = w.write(b"0123456789").unwrap();
  println!("  write returned {n}");
  let e = w.write_all(b"xyz").unwrap_err();
  println!("  write_all: {:?} {}", e.kind(), e);
  println!("  inner = {:?}", w.get_ref().get_ref());

  println!("== Reader");
  let mut r = Bytes::from_static(b"line one\nli").chain(Bytes::from_static(b"ne two\n")).reader();
  println!("  fill_buf = {:?}", Bytes::copy_from_slice(r.fill_buf().unwrap()));
  let mut s = String::new();
  r.read_line(&mut s).unwrap();
  r.read_line(&mut s).unwrap();
  println!("  two read_line = {s:?}");
  let mut rest = Vec::new();
  let n = r.read_to_end(&mut rest).unwrap();
  println!("  read_to_end = {n}");

  println!("== io::copy Reader -> Writer");
  let mut src = Bytes::from_static(b"abc").chain(Bytes::from_static(b"def")).reader();
  let mut dst = BytesMut::new().writer();
  let n = io::copy(&mut src, &mut dst).unwrap();
  println!("  copied {n}: {:?}", dst.into_inner());
}

跑 3 次,输出相同:

== Writer
  write returned 8
  write_all: WriteZero failed to write whole buffer
  inner = b"01234567"
== Reader
  fill_buf = b"line one\nli"
  two read_line = "line one\nline two\n"
  read_to_end = 0
== io::copy Reader -> Writer
  copied 6: b"abcdef"

write 给了 10 个字节,Limit 只剩 8,返回 8;再 write_all 时 write 返回 0,得到 WriteZero。fill_buf 只给了第一段 line one\nli,第二个 read_line 照样读出了跨段的 line two\n。

IntoIter:逐字节迭代

IntoIter 把任何 Buf 变成 Iterator<Item = u8>:

impl<T: Buf> Iterator for IntoIter<T> {
    type Item = u8;

    fn next(&mut self) -> Option<u8> {
        if !self.inner.has_remaining() {
            return None;
        }

        let b = self.inner.chunk()[0];
        self.inner.advance(1);

        Some(b)
    }

    fn size_hint(&self) -> (usize, Option<usize>) {
        let rem = self.inner.remaining();
        (rem, Some(rem))
    }
}

每次取当前 chunk 的第一个字节再前进 1。has_remaining 为真时 chunk 一定非空(Buf 的约定),所以 [0] 不会越界,跨段也不用特殊处理。size_hint 是精确的,于是它还实现了 ExactSizeIterator。

得到它的方式是 IntoIterator:Bytes、BytesMut 和 Chain 按值 into_iter() 得到 IntoIter<Self>,产出 u8。按引用就不一样了:&Bytes 和 &BytesMut 的 into_iter() 是切片的 core::slice::Iter,产出 &u8,不经过 Buf。别的 Buf 可以用 IntoIter::new 包(1.4.0 起是公开的,#581)。

use bytes::{Buf, Bytes};

fn main() {
  let it = Bytes::from_static(b"ab").chain(Bytes::from_static(b"cd")).into_iter();
  println!("size_hint {:?}", it.size_hint());
  println!("collect {:?}", it.map(|b| b as char).collect::<String>());

  let b = Bytes::from_static(b"xy");
  let by_ref: Vec<&u8> = (&b).into_iter().collect();
  println!("&Bytes -> {by_ref:?}");
  let by_val: Vec<u8> = b.into_iter().collect();
  println!("Bytes  -> {by_val:?}");
}

跑 3 次,输出相同:

size_hint (4, Some(4))
collect "abcd"
&Bytes -> [120, 121]
Bytes  -> [120, 121]

逐字节迭代每一步都是一次 chunk 加一次 advance,解析大块数据时比直接在 chunk() 上操作慢,适合小量的数据或需要 Iterator 接口的地方。

动脑筋答案

== Take
  remaining 5 chunk b"abc"
  chunks_vectored 2: [3, 2]
  after advance(4): remaining 1 limit 1 inner remaining 4
  panicked: assertion failed: cnt <= self.limit
  advance(2) ok = false
== Limit
  remaining_mut 4
  chunk_mut len 4 (inner cap now 64)
  panicked: advance out of bounds: the len is 4 but advancing by 5
  put_slice(5 bytes) ok = false
  after put_slice(4): [49, 50, 51, 52] remaining_mut 0

跑了 3 次,输出相同。chunks_vectored 打印 2 段,长度 3 和 2;put_slice(b"12345") 的 panic 是 advance out of bounds: the len is 4 but advancing by 5。

  • Take 的 chunk 只有 abc(内层 Chain 的第一段,没到 limit);chunks_vectored 让内层填出 abc(3)和 defgh(5),抄到 dst 时第二段碰到剩余的 limit 2,截成 2 并返回。
  • advance(4) 之后 limit 剩 1,内层 Chain 还剩 8 − 4 = 4;advance(2) 超过 limit,是那个不带信息的 assert!。
  • Limit 的 remaining_mut 是 min(isize::MAX, 4) = 4。chunk_mut 让内层 Vec 先 reserve(64),内层容量变成 64,交出来的只截到 4。
  • put_slice 没被 Limit 覆盖,走默认实现;开头的检查发现 remaining_mut() 是 4、要写 5,调用 panic_advance,信息原文就是这句(用的是写方向,字面上仍是「advance out of bounds」)。panic 发生在写入之前,所以随后写 4 个字节正好写满,remaining_mut 变 0。

小结

  • 六个适配器都只改写语义要求改的方法,其余 get_* / put_* 继承默认实现。Reader、Writer 需要 std feature。
  • Chain::chunk 只返回第一段非空数据,chunks_vectored 才能一次拿到两段。把 chunk() 当全部数据的解析器在跨段时会找不到分隔符或切片越界;定长字段用 get_*,变长的先保证连续或逐段处理。
  • Chain::copy_to_bytes 不跨边界时转给那一段,Bytes 不拷贝;跨边界时分配一块拷贝(#481,1.1.0)。remaining / remaining_mut 用饱和加法,因为会增长的缓冲把 remaining_mut 报得接近 isize::MAX(#488,1.2.0)。
  • Take 把 remaining、chunk、advance、copy_to_bytes、chunks_vectored 都截到 limit;chunks_vectored 的覆盖是为了 h2 的 vectored write(#617,1.10.0),最多取 16 段。
  • Limit 只覆盖三个必需方法,经过它的写入走默认 put_slice 循环,超限时 panic 在写入之前。
  • Reader 的 fill_buf / consume 就是 chunk / advance;Writer::write 会短写,写满后 write_all 得到 WriteZero(1.9.0 起用 put_slice)。
  • IntoIter 每步 chunk()[0] 加 advance(1),长度精确;Bytes 按值迭代产出 u8,按引用是切片迭代器。

💬 关联讨论

发起讨论
加载中...