You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Rust中如何回滚BytesMut读取位置重复调用get_u64方法

BytesMut读指针回滚实现方案

BytesMut实现的Buftrait默认是流语义,读指针只会向前移动,原生没有提供安全的公开回退API,你可以根据实际场景选择以下三种实现方式:


方案1:用std::io::Cursor包装(最推荐,全安全代码,零开销)

Cursor本身就是为支持随机访问读写位置设计的,Cursor<BytesMut>完整实现了Buf和BufMuttrait,所有get_u64/put_u64等方法和直接使用BytesMut完全一致,同时提供了position()获取当前读位置、set_position()设置读位置的方法,可以任意回滚,没有unsafe代码,也没有额外性能损耗。

示例代码:

use bytes::{Buf, BufMut, BytesMut};
use std::io::Cursor;

fn main() {
    // 用Cursor包装BytesMut
    let mut buf = Cursor::new(BytesMut::with_capacity(1024));
    buf.put_u64(100);
    // 写入完成后将读指针重置到起始位置(写入操作会移动Cursor指针)
    buf.set_position(0);

    // 第一次读取
    let val1 = buf.get_u64();
    assert_eq!(val1, 100);
    assert_eq!(buf.position(), 8); // 读完u64指针移动到偏移8的位置

    // 回滚到起始位置
    buf.set_position(0);
    // 第二次读取,可正常拿到相同值
    let val2 = buf.get_u64();
    assert_eq!(val2, 100);
}

如果需要回滚到任意读取前的位置,只需要在读取前调用let checkpoint = buf.position()保存位置,需要回滚时调用buf.set_position(checkpoint)即可。


方案2:快照法(适合回滚到固定检查点的场景,全安全代码)

BytesMut的clone()是O(1)的浅拷贝,仅增加引用计数,不会立刻复制内存(只有后续修改缓冲区时才会触发写时复制)。你可以在需要设置回滚点的时候clone一份当前的BytesMut作为快照,回滚时直接把原变量替换为快照即可。

示例代码:

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

fn main() {
    let mut buf = BytesMut::with_capacity(1024);
    buf.put_u64(100);

    // 设置回滚快照
    let snapshot = buf.clone();
    // 第一次读取
    let val1 = buf.get_u64();
    assert_eq!(val1, 100);
    assert_eq!(buf.remaining(), 0); // 读完后剩余可读长度为0

    // 回滚到快照位置
    buf = snapshot;
    // 第二次读取
    let val2 = buf.get_u64();
    assert_eq!(val2, 100);
}

这个方案非常适合协议解析类场景:先尝试解析一段数据,如果解析失败(比如数据不完整)就直接回滚到快照位置,等后续数据到齐再重新解析。


方案3:原地回退(极致性能场景,需使用unsafe)

BytesMut内部提供了set_start(usize)方法可以直接设置读指针相对于缓冲区起始的偏移,但是这个方法是unsafe的,你必须自己保证新的读指针位置合法:不能小于缓冲区起始位置,不能超过当前写指针的位置(否则会读到未初始化的内存,触发未定义行为)。

可以封装一个带边界检查的安全回滚方法:

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

/// 回滚指定长度的读指针,回滚长度不能超过已经读取的总字节数
unsafe fn rollback(buf: &mut BytesMut, rollback_len: usize) {
    let new_start = buf.as_ptr().offset(-(rollback_len as isize));
    buf.set_start(new_start as usize);
}

fn main() {
    let mut buf = BytesMut::with_capacity(1024);
    buf.put_u64(100);

    let val1 = buf.get_u64();
    assert_eq!(val1, 100);
    // 回滚8字节(一个u64的长度)
    unsafe { rollback(&mut buf, 8); }
    let val2 = buf.get_u64();
    assert_eq!(val2, 100);
}

注意:这个方案对边界检查的要求非常高,不推荐在普通业务代码中使用,优先选择前两种安全方案。


注意事项

  • 禁止在没有做严格边界检查的情况下直接调用set_start,会导致内存安全问题。
  • 如果需要频繁随机调整读位置,优先选择Cursor方案,代码最简洁,安全性最高。

内容的提问来源于stack exchange,提问作者yang guangyue

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 18:15:58