Skip to main content

zero_copy_reuse/
zero_copy_reuse.rs

1//! 零拷贝与解码存储复用(示例 4/6)。
2//!
3//! 运行:`cargo run -p nextjson --example zero_copy_reuse`
4//!
5//! 演示 NextJson 的第三根支柱——reuse-first:
6//!
7//! 1. **借用输入**:`&str` / `Bytes` 字段直接从输入切片借用(未转义字符串
8//!    零分配),通过指针范围断言证明没有发生拷贝;
9//! 2. **`DecodeSlot` 就地解码**:`nextdecode_into` 把值写进调用方提供的槽,
10//!    在持续解码循环里复用同一个槽与缓冲区,避免每次消息重建存储。
11
12use nextjson::formats::Format;
13use nextjson::{Bytes, DecodeSlot, Decoder, NsonDeserialize, NsonSerialize};
14
15/// 借用型结构:`event` 与 `payload` 的生命周期与输入切片绑定。
16/// `#[njson(borrow)]` 让 derive 生成 `'de: 'a` 边界(serde 的 `#[serde(borrow)]` 语义)。
17#[derive(Debug, PartialEq, NsonSerialize, NsonDeserialize)]
18struct Header<'a> {
19    #[njson(borrow)]
20    event: &'a str,
21    #[njson(borrow)]
22    payload: Bytes<'a>,
23    seq: u64,
24}
25
26/// 拥有型等价物:可在任意格式(包括经 Value 中继的二进制格式)往返。
27/// 借用只对直接词法解析的 JSON 解码器成立——经 CBOR/MsgPack 中继的路径
28/// 必然经过一颗拥有的 Value 树,因此借用字段无法在其中存活。
29#[derive(Debug, PartialEq, NsonSerialize, NsonDeserialize)]
30struct HeaderOwned {
31    event: String,
32    payload: Vec<u8>,
33    seq: u64,
34}
35
36fn main() -> nextjson::Result<()> {
37    // ---- 1. 零拷贝借用 ----
38    // payload 用未转义字符串书写:JSON 解码器对未转义字符串返回 Cow::Borrowed,
39    // 从而 `Bytes` 能直接借用输入切片(若写成 `[10,20,30,40]` 数组则必然拷贝,
40    // 与 serde 的 `&[u8]` 借用语义一致)。
41    let input = br#"{"event":"market.tick","payload":"raw-bytes","seq":7}"#;
42    let header: Header = nextjson::from_slice(input)?;
43    println!(
44        "借用解码: event = {:?}, payload = {:?}, seq = {}",
45        header.event,
46        header.payload.as_bytes(),
47        header.seq
48    );
49
50    // 指针范围断言:event / payload 必须落在输入切片内(零拷贝的直接证据)。
51    let input_start = input.as_ptr() as usize;
52    let input_end = input_start + input.len();
53    let event_ptr = header.event.as_ptr() as usize;
54    let payload_ptr = header.payload.as_bytes().as_ptr() as usize;
55    assert!(
56        (input_start..input_end).contains(&event_ptr),
57        "event 必须借用输入,不能拷贝"
58    );
59    assert!(
60        (input_start..input_end).contains(&payload_ptr),
61        "payload 必须借用输入,不能拷贝"
62    );
63    println!("指针断言通过: event 与 payload 均直接指向输入切片");
64
65    // 二进制格式通过 Value 中继,无法借用——改用拥有型等价物往返,
66    // 证明同一数据模型在 CBOR 中原生字节串无损。
67    let owned = HeaderOwned {
68        event: header.event.to_owned(),
69        payload: header.payload.as_bytes().to_vec(),
70        seq: header.seq,
71    };
72    let cbor = nextjson::formats::Cbor.encode(&owned)?;
73    let back: HeaderOwned = nextjson::formats::Cbor.decode(&cbor)?;
74    assert_eq!(back, owned);
75    println!("拥有型等价物经 CBOR 原生字节串往返一致: {} B", cbor.len());
76
77    // ---- 2. 就地解码 + 槽复用 ----
78    // 持续解码场景:同一个槽反复用于下一条消息,不重新分配存储。
79    let messages = [
80        br#"{"event":"a","payload":"x","seq":1}"#.as_slice(),
81        br#"{"event":"b","payload":"yy","seq":2}"#.as_slice(),
82        br#"{"event":"c","payload":"zzz","seq":3}"#.as_slice(),
83    ];
84
85    // 复用同一个 DecodeSlot<Header>,逐条 nextdecode_into。
86    let mut slot = DecodeSlot::<Header>::new();
87    let mut decoded: Vec<Header> = Vec::new();
88    for raw in &messages {
89        let mut decoder = Decoder::new(raw);
90        Header::nextdecode_into(&mut decoder, &mut slot)?;
91        decoder.end()?;
92        // take 取出本条,槽回归空,下一条继续复用。
93        decoded.push(slot.take().expect("解码成功必须写入槽"));
94    }
95    assert_eq!(decoded[0].seq, 1);
96    assert_eq!(decoded[1].event, "b");
97    assert_eq!(decoded[2].payload.as_bytes(), b"zzz");
98    println!(
99        "槽复用: 3 条消息共用 1 个 DecodeSlot,逐条解码成功: {:?}",
100        decoded.iter().map(|h| h.seq).collect::<Vec<_>>()
101    );
102
103    // 显式证明复用:一个槽连续解两条,且第二条完成后 is_initialized 恢复。
104    let mut slot2 = DecodeSlot::<Header>::new();
105    let mut d1 = Decoder::new(messages[0]);
106    Header::nextdecode_into(&mut d1, &mut slot2)?;
107    d1.end()?;
108    let first = slot2.take().unwrap();
109    let mut d2 = Decoder::new(messages[1]);
110    Header::nextdecode_into(&mut d2, &mut slot2)?;
111    d2.end()?;
112    let second = slot2.take().unwrap();
113    assert_eq!((first.seq, second.seq), (1, 2));
114    println!(
115        "同一个槽连续承载两条消息: seq {} -> seq {}",
116        first.seq, second.seq
117    );
118
119    Ok(())
120}