1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
use bytes::{Bytes, BytesMut};
use nm::Event;
use crate::BytesView;
impl BytesView {
/// Returns a `bytes::Bytes` that contains the same byte sequence.
///
/// # Example
///
/// ```
/// # let memory = bytesbuf::mem::GlobalPool::new();
/// use bytes::Buf;
/// use bytesbuf::BytesView;
///
/// let view = BytesView::copied_from_slice(b"\x12\x34\x56\x78", &memory);
///
/// let mut bytes = view.to_bytes();
///
/// // Consume the data using the bytes crate's Buf trait.
/// assert_eq!(bytes.get_u16(), 0x1234);
/// assert_eq!(bytes.get_u16(), 0x5678);
/// assert!(!bytes.has_remaining());
/// ```
///
/// # Performance
///
/// This operation is zero-copy if the sequence is backed by a single consecutive
/// slice of memory capacity.
///
/// If the sequence is backed by multiple slices of memory capacity, the data will be copied
/// to a new `Bytes` instance backed by new memory capacity from the Rust global allocator.
///
/// **You generally want to avoid this conversion in performance-sensitive code.**
///
/// This conversion always requires a small dynamic memory allocation for
/// metadata, so avoiding conversions is valuable even if zero-copy.
///
/// # Why prefer `.to_bytes()` over `.into()`?
///
/// While conversion via `.into()` is supported, prefer calling `.to_bytes()` explicitly
/// because the conversion is not guaranteed to be a cheap operation and may involve data
/// copying. An explicit `.to_bytes()` call makes the conversion more obvious and easier
/// to catch in reviews.
#[must_use]
#[expect(clippy::missing_panics_doc, reason = "only unreachable panics")]
pub fn to_bytes(&self) -> Bytes {
if self.spans_reversed.is_empty() {
TO_BYTES_SHARED.with(|x| x.observe(0));
Bytes::new()
} else if self.spans_reversed.len() == 1 {
// We are a single-span view, which can always be zero-copy represented.
TO_BYTES_SHARED.with(|x| x.observe(self.len()));
Bytes::from_owner(self.spans_reversed.first().expect("we verified there is one span").clone())
} else {
// We must copy, as Bytes can only represent consecutive spans of data.
let mut bytes = BytesMut::with_capacity(self.len());
for span in self.spans_reversed.iter().rev() {
bytes.extend_from_slice(span);
}
debug_assert_eq!(self.len(), bytes.len());
TO_BYTES_COPIED.with(|x| x.observe(self.len()));
bytes.freeze()
}
}
}
impl From<BytesView> for Bytes {
fn from(value: BytesView) -> Self {
value.to_bytes()
}
}
thread_local! {
static TO_BYTES_SHARED: Event = Event::builder()
.name("bytesbuf_view_to_bytes_shared")
.build();
static TO_BYTES_COPIED: Event = Event::builder()
.name("bytesbuf_view_to_bytes_copied")
.build();
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg(test)]
mod tests {
use bytes::Buf;
use new_zealand::nz;
use super::*;
use crate::mem::testing::{TransparentMemory, std_alloc_block};
#[test]
fn view_to_bytes() {
let mut builder = std_alloc_block::allocate(nz!(100)).into_span_builder();
builder.put_slice(&1234_u64.to_ne_bytes());
builder.put_slice(&5678_u64.to_ne_bytes());
let span1 = builder.consume(nz!(8));
let span2 = builder.consume(nz!(8));
let view_single_span = BytesView::from_spans(vec![span1.clone()]);
let view_multi_span = BytesView::from_spans(vec![span1, span2]);
let mut bytes = view_single_span.to_bytes();
assert_eq!(8, bytes.len());
assert_eq!(1234, bytes.get_u64_ne());
let mut bytes = view_single_span.to_bytes();
assert_eq!(8, bytes.len());
assert_eq!(1234, bytes.get_u64_ne());
let mut bytes = view_multi_span.to_bytes();
assert_eq!(16, bytes.len());
assert_eq!(1234, bytes.get_u64_ne());
assert_eq!(5678, bytes.get_u64_ne());
}
#[test]
fn empty_view_to_bytes() {
let view = BytesView::default();
let bytes = view.to_bytes();
assert_eq!(0, bytes.len());
}
#[test]
fn into_bytes() {
let memory = TransparentMemory::new();
let view = BytesView::copied_from_slice(b"Hello, world!", &memory);
let bytes: Bytes = view.into();
assert_eq!(bytes.as_ref(), b"Hello, world!");
}
#[test]
fn test_view_to_bytes() {
let memory = TransparentMemory::new();
let view = BytesView::copied_from_slice(b"Hello, world!", &memory);
let view_chunk_ptr = view.first_slice().as_ptr();
let bytes = view.to_bytes();
assert_eq!(bytes.as_ref(), b"Hello, world!");
// We expect this to be zero-copy since we used the passthrough allocator.
assert_eq!(bytes.as_ptr(), view_chunk_ptr);
}
#[test]
fn test_multi_block_view_to_bytes() {
let memory = TransparentMemory::new();
let hello = BytesView::copied_from_slice(b"Hello, ", &memory);
let world = BytesView::copied_from_slice(b"world!", &memory);
let view = BytesView::from_views([hello, world]);
let bytes = view.to_bytes();
assert_eq!(bytes.as_ref(), b"Hello, world!");
}
}