diff --git a/.spelling b/.spelling index be3c9863..040d9bbd 100644 --- a/.spelling +++ b/.spelling @@ -33,6 +33,7 @@ bool branch_name btree_map buildable +bytesbuf C #-Rust C-BITFLAG C-CONV @@ -155,6 +156,7 @@ newtypes Newtypes Nomicon non-mockable +nonoverlapping ns nuget ok @@ -192,6 +194,7 @@ sans-io SCCACHE scopeguard SDKs +seeked SemVer serde Serde @@ -201,6 +204,7 @@ serializable setsockopt skimmable SLAs +smallvec stdlib struct structs diff --git a/Cargo.lock b/Cargo.lock index 683c0920..496c6ce4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -96,6 +96,7 @@ dependencies = [ "mutants", "new_zealand", "nm", + "num-traits", "smallvec", "static_assertions", "testing_aids", diff --git a/Cargo.toml b/Cargo.toml index 1ab8c95c..594b0ed8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -59,6 +59,7 @@ mockall = { version = "0.14.0", default-features = false } mutants = { version = "0.0.3", default-features = false } new_zealand = { version = "1.0.1", default-features = false } nm = { version = "0.1.21", default-features = false } +num-traits = { version = "0.2.19", default-features = false } once_cell = { version = "1.21.3", default-features = false } pin-project-lite = { version = "0.2.13", default-features = false } pretty_assertions = { version = "1.4.1", default-features = false } @@ -97,7 +98,10 @@ unsafe_op_in_unsafe_fn = "warn" unused_lifetimes = "warn" # Allow cfg attributes for coverage builds and docs.rs builds -unexpected_cfgs = { level = "warn", check-cfg = ['cfg(coverage,coverage_nightly)', 'cfg(docsrs)'] } +unexpected_cfgs = { level = "warn", check-cfg = [ + 'cfg(coverage,coverage_nightly)', + 'cfg(docsrs)', +] } [workspace.lints.clippy] cargo = { level = "warn", priority = -1 } diff --git a/crates/bytesbuf/Cargo.toml b/crates/bytesbuf/Cargo.toml index 85a9624a..51ee472f 100644 --- a/crates/bytesbuf/Cargo.toml +++ b/crates/bytesbuf/Cargo.toml @@ -22,6 +22,8 @@ allowed_external_types = [ "bytes::buf::buf_impl::Buf", "bytes::buf::uninit_slice::UninitSlice", "bytes::buf::buf_mut::BufMut", + "num_traits::ops::bytes::FromBytes", + "num_traits::ops::bytes::ToBytes", ] [package.metadata.docs.rs] @@ -36,6 +38,7 @@ bytes = { workspace = true, features = ["std"] } infinity_pool.workspace = true new_zealand.workspace = true nm.workspace = true +num-traits.workspace = true smallvec = { workspace = true, features = ["const_new", "union"] } [dev-dependencies] diff --git a/crates/bytesbuf/README.md b/crates/bytesbuf/README.md index 5ca97cf6..b98fb992 100644 --- a/crates/bytesbuf/README.md +++ b/crates/bytesbuf/README.md @@ -13,38 +13,41 @@ -Manipulate sequences of bytes for efficient I/O. +Create and manipulate byte sequences for efficient I/O. -A [`BytesView`][__link0] is a view over a logical sequence of zero or more bytes -stored in memory, similar to a slice `&[u8]` but with some key differences: +A byte sequence is a logical sequence of zero or more bytes stored in memory, +similar to a slice `&[u8]` but with some key differences: * The bytes in a byte sequence are not required to be consecutive in memory. -* The bytes in a byte sequence are always immutable, even if you own the [`BytesView`][__link1]. +* The bytes in a byte sequence are always immutable. In practical terms, you may think of a byte sequence as a `Vec>` whose contents are -treated as one logical sequence of bytes. The types in this crate provide a way to work with -byte sequences using an API that is reasonably convenient while also being compatible with -the requirements of high-performance zero-copy I/O operations. +treated as one logical sequence of bytes. Byte sequences are created via [`BytesBuf`][__link0] and +consumed via [`BytesView`][__link1]. ## Consuming Byte Sequences -The standard model for using bytes of data from a [`BytesView`][__link2] is to consume them via the -[`bytes::buf::Buf`][__link3] trait, which is implemented by [`BytesView`][__link4]. +A byte sequence is typically consumed by reading its contents. This is done via the +[`BytesView`][__link2] type, which is a view over a byte sequence. When reading data, the read +bytes are removed from the view, shrinking it to only the remaining bytes. -There are many helper methods on this trait that will read bytes from the beginning of the -sequence and simultaneously remove the read bytes from the sequence, shrinking it to only -the remaining bytes. +There are many helper methods on this type for easily consuming bytes from the view: + +* [`get_num_le::()`][__link3] reads numbers. Big-endian/native-endian variants also exist. +* [`get_byte()`][__link4] reads a single byte. +* [`copy_to_slice()`][__link5] copies bytes into a provided slice. +* [`copy_to_uninit_slice()`][__link6] copies bytes into a provided uninitialized slice. +* [`as_read()`][__link7] creates a `std::io::Read` adapter for reading bytes via standard I/O methods. ```rust -use bytes::Buf; use bytesbuf::BytesView; fn consume_message(mut message: BytesView) { // We read the message and calculate the sum of all the words in it. let mut sum: u64 = 0; - while message.has_remaining() { - let word = message.get_u64(); + while !message.is_empty() { + let word = message.get_num_le::(); sum = sum.saturating_add(word); } @@ -52,77 +55,75 @@ fn consume_message(mut message: BytesView) { } ``` -If the helper methods are not sufficient, you can access the contents via byte slices using the -more fundamental methods of the [`bytes::buf::Buf`][__link5] trait such as: +If the helper methods are not sufficient, you can access the byte sequence via byte slices using the +following fundamental methods that underpin the convenience methods: -* [`chunk()`][__link6], which returns a slice of bytes from the beginning of the sequence. The +* [`first_slice()`][__link8], which returns the first slice of bytes that makes up the byte sequence. The length of this slice is determined by the inner structure of the byte sequence and it may not - contain all the bytes in the sequence. -* [`advance()`][__link7], which removes bytes from the beginning of the sequence, advancing the - head to a new position. When you advance past the slice returned by `chunk()`, the next - call to `chunk()` will return a new slice of bytes starting from the new head position. -* [`chunks_vectored()`][__link8], which returns multiple slices of bytes from the beginning of the - sequence. This can be desirable for advanced access models that can consume multiple - chunks of data at the same time. + contain all the bytes. +* [`advance()`][__link9], which marks bytes from the beginning of [`first_slice()`][__link10] as read, shrinking the + view of the byte sequence by the corresponding amount and moving remaining data up to the front. + When you advance past the slice returned by [`first_slice()`][__link11], the next call to [`first_slice()`][__link12] + will return a new slice of bytes starting from the new front position of the view. ```rust -use bytes::Buf; use bytesbuf::BytesView; -let len = sequence.len(); -let mut chunk_lengths = Vec::new(); +let len = bytes.len(); +let mut slice_lengths = Vec::new(); -while sequence.has_remaining() { - let chunk = sequence.chunk(); - chunk_lengths.push(chunk.len()); +while !bytes.is_empty() { + let slice = bytes.first_slice(); + slice_lengths.push(slice.len()); - // We have completed processing this chunk, all we wanted was to know its length. - sequence.advance(chunk.len()); + // We have completed processing this slice. All we wanted was to know its length. + // We can now mark this slice as consumed, revealing the next slice for inspection. + bytes.advance(slice.len()); } -println!("Inspected a sequence of {len} bytes with chunk lengths: {chunk_lengths:?}"); +println!("Inspected a view over {len} bytes with slice lengths: {slice_lengths:?}"); ``` To reuse a byte sequence, clone it before consuming the contents. This is a cheap zero-copy operation. ```rust -use bytes::Buf; use bytesbuf::BytesView; -assert_eq!(sequence.len(), 16); +assert_eq!(bytes.len(), 16); -let mut sequence_clone = sequence.clone(); -assert_eq!(sequence_clone.len(), 16); +let mut bytes_clone = bytes.clone(); +assert_eq!(bytes_clone.len(), 16); -_ = sequence_clone.get_u64(); -assert_eq!(sequence_clone.len(), 8); +// Consume 8 bytes from the front. +_ = bytes_clone.get_num_le::(); +assert_eq!(bytes_clone.len(), 8); -// Operations on the clone have no effect on the original sequence. -assert_eq!(sequence.len(), 16); +// Operations on the clone have no effect on the original view. +assert_eq!(bytes.len(), 16); ``` ## Producing Byte Sequences For creating a byte sequence, you first need some memory capacity to put the bytes into. This -means you need a memory provider, which is a type that implements the [`Memory`][__link9] trait. +means you need a memory provider, which is a type that implements the [`Memory`][__link13] trait. Obtaining a memory provider is generally straightforward. Simply use the first matching option from the following list: 1. If you are creating byte sequences for the purpose of submitting them to a specific - object of a known type (e.g. writing them to a network connection), the target type will - typically implement the [`HasMemory`][__link10] trait, which gives you a suitable memory - provider instance via [`HasMemory::memory`][__link11]. Use it - this memory provider will + object of a known type (e.g. writing them to a `TcpConnection`), the target type will + typically implement the [`HasMemory`][__link14] trait, which gives you a suitable memory + provider instance via [`HasMemory::memory()`][__link15]. Use this as the memory provider - it will give you memory with the configuration that is optimal for delivering bytes to that - specific instance. + specific consumer. 1. If you are creating byte sequences as part of usage-neutral data processing, obtain an - instance of [`GlobalPool`][__link12]. In a typical web application framework, this is a service - exposed by the application framework. In a different context (e.g. example or test code - with no framework), you can create your own instance via `GlobalPool::new()`. + instance of a shared [`GlobalPool`][__link16]. In a typical web application, the global memory pool + is a service exposed by the application framework. In a different context (e.g. example + or test code with no framework), you can create your own instance via `GlobalPool::new()`. Once you have a memory provider, you can reserve memory from it by calling -[`Memory::reserve`][__link13] on it. This returns a [`BytesBuf`][__link14] with the requested +[`Memory::reserve()`][__link17] on it. This returns a [`BytesBuf`][__link18] with the requested memory capacity. ```rust @@ -130,161 +131,159 @@ use bytesbuf::Memory; let memory = connection.memory(); -let mut sequence_builder = memory.reserve(100); +let mut buf = memory.reserve(100); ``` -Now that you have the memory capacity and a [`BytesBuf`][__link15], you can fill the memory -capacity with bytes of data. The standard pattern for this is to use the -[`bytes::buf::BufMut`][__link16] trait, which is implemented by [`BytesBuf`][__link17]. +Now that you have the memory capacity in a [`BytesBuf`][__link19], you can fill the memory +capacity with bytes of data. Creating byte sequences in a [`BytesBuf`][__link20] is an +append-only process - you can only add data to the end of the buffered sequence. + +There are many helper methods on [`BytesBuf`][__link21] for easily appending bytes to the buffer: -Helper methods on this trait allow you to write bytes to the sequence builder up to the -extent of the reserved memory capacity. +* [`put_num_le::()`][__link22], which appends numbers. Big-endian/native-endian variants also exist. +* [`put_slice()`][__link23], which appends a slice of bytes. +* [`put_byte()`][__link24], which appends a single byte. +* [`put_byte_repeated()`][__link25], which appends multiple repetitions of a byte. +* [`put_bytes()`][__link26], which appends an existing [`BytesView`][__link27]. ```rust -use bytes::buf::BufMut; use bytesbuf::Memory; let memory = connection.memory(); -let mut sequence_builder = memory.reserve(100); +let mut buf = memory.reserve(100); -sequence_builder.put_u64(1234); -sequence_builder.put_u64(5678); -sequence_builder.put(b"Hello, world!".as_slice()); +buf.put_num_be(1234_u64); +buf.put_num_be(5678_u64); +buf.put_slice(*b"Hello, world!"); ``` -If the helper methods are not sufficient, you can append contents via mutable byte slices -using the more fundamental methods of the [`bytes::buf::BufMut`][__link18] trait such as: +If the helper methods are not sufficient, you can write contents directly into mutable byte slices +using the fundamental methods that underpin the convenience methods: -* [`chunk_mut()`][__link19], which returns a mutable slice of bytes from the beginning of the - sequence builder’s unused capacity. The length of this slice is determined by the inner - structure of the sequence builder and it may not contain all the capacity that has been - reserved. -* [`advance_mut()`][__link20], which declares that a number of bytes from the beginning of the - unused capacity have been initialized with data and are no longer unused. This will - mark these bytes as valid for reading and advance `chunk_mut()` to the next slice if the - current one has been completely filled. +* [`first_unfilled_slice()`][__link28], which returns a mutable slice of bytes from the beginning of the + buffer’s remaining capacity. The length of this slice is determined by the inner memory layout + of the buffer and it may not contain all the capacity that has been reserved. +* [`advance()`][__link29], which declares that a number of bytes at the beginning of [`first_unfilled_slice()`][__link30] + have been initialized with data and are no longer unused. This will mark these bytes as valid for + consumption and advance [`first_unfilled_slice()`][__link31] to the next slice of unused memory capacity + if the current slice has been completely filled. -See `examples/mem_chunk_write.rs` for an example of how to use these methods. +See `examples/bb_slice_by_slice_write.rs` for an example of how to use these methods. If you do not know exactly how much memory you need in advance, you can extend the sequence -builder capacity on demand if you run out by calling [`BytesBuf::reserve`][__link21], -which will reserve more memory capacity. You can use [`bytes::buf::BufMut::remaining_mut()`][__link22] -on the sequence builder to identify how much unused memory capacity is available for writing. +builder capacity on demand by calling [`BytesBuf::reserve()`][__link32]. You can use [`remaining_capacity()`][__link33] +to identify how much unused memory capacity is available. ```rust -use bytes::buf::BufMut; use bytesbuf::Memory; let memory = connection.memory(); -let mut sequence_builder = memory.reserve(100); +let mut buf = memory.reserve(100); -// .. write some data into the sequence builder .. +// .. write some data into the buffer .. // We discover that we need 80 additional bytes of memory! No problem. -sequence_builder.reserve(80, &memory); +buf.reserve(80, &memory); // Remember that a memory provider can always provide more memory than requested. -assert!(sequence_builder.capacity() >= 100 + 80); -assert!(sequence_builder.remaining_mut() >= 80); +assert!(buf.capacity() >= 100 + 80); +assert!(buf.remaining_capacity() >= 80); ``` -When you have filled the memory capacity with the bytes you wanted to write, you can consume -the data in the sequence builder, turning it into a [`BytesView`][__link23] of immutable bytes. +When you have filled the memory capacity with the contents of the byte sequence, you can consume +the data in the buffer as a [`BytesView`][__link34] over immutable bytes. ```rust -use bytes::buf::BufMut; use bytesbuf::Memory; let memory = connection.memory(); -let mut sequence_builder = memory.reserve(100); +let mut buf = memory.reserve(100); -sequence_builder.put_u64(1234); -sequence_builder.put_u64(5678); -sequence_builder.put(b"Hello, world!".as_slice()); +buf.put_num_be(1234_u64); +buf.put_num_be(5678_u64); +buf.put_slice(*b"Hello, world!"); -let message = sequence_builder.consume_all(); +let message = buf.consume_all(); ``` -This can be done piece by piece, and you can continue writing to the sequence builder +This can be done piece by piece, and you can continue writing to the buffer after consuming some already written bytes. ```rust -use bytes::buf::BufMut; use bytesbuf::Memory; let memory = connection.memory(); -let mut sequence_builder = memory.reserve(100); +let mut buf = memory.reserve(100); -sequence_builder.put_u64(1234); -sequence_builder.put_u64(5678); +buf.put_num_be(1234_u64); +buf.put_num_be(5678_u64); -let first_8_bytes = sequence_builder.consume(8); -let second_8_bytes = sequence_builder.consume(8); +let first_8_bytes = buf.consume(8); +let second_8_bytes = buf.consume(8); -sequence_builder.put(b"Hello, world!".as_slice()); +buf.put_slice(*b"Hello, world!"); -let final_contents = sequence_builder.consume_all(); +let final_contents = buf.consume_all(); ``` -If you already have a [`BytesView`][__link24] that you want to write into a [`BytesBuf`][__link25], call -[`BytesBuf::append()`][__link26]. This is a highly efficient zero-copy operation -that reuses the memory capacity of the sequence you are appending. +If you already have a [`BytesView`][__link35] that you want to write into a [`BytesBuf`][__link36], call +[`BytesBuf::put_bytes()`][__link37]. This is a highly efficient zero-copy operation +that reuses the memory capacity of the view you are appending. ```rust -use bytes::buf::BufMut; use bytesbuf::Memory; let memory = connection.memory(); let mut header_builder = memory.reserve(16); -header_builder.put_u64(1234); +header_builder.put_num_be(1234_u64); let header = header_builder.consume_all(); -let mut sequence_builder = memory.reserve(128); -sequence_builder.append(header); -sequence_builder.put(b"Hello, world!".as_slice()); +let mut buf = memory.reserve(128); +buf.put_bytes(header); +buf.put_slice(*b"Hello, world!"); ``` -Note that there is no requirement that the memory capacity of the sequence builder and the -memory capacity of the sequence being appended come from the same memory provider. It is valid +Note that there is no requirement that the memory capacity of the buffer and the +memory capacity of the view being appended come from the same memory provider. It is valid to mix and match memory from different providers, though this may disable some optimizations. ## Implementing APIs that Consume Byte Sequences If you are implementing a type that accepts byte sequences, you should implement the -[`HasMemory`][__link27] trait to make it possible for the caller to use optimally -configured memory. +[`HasMemory`][__link38] trait to make it possible for the caller to use optimally +configured memory when creating the byte sequences for input to your type. Even if the implementation of your type today is not capable of taking advantage of optimizations that depend on the memory configuration, it may be capable of doing so in the future or may, today or in the future, pass the data to another type that -implements [`HasMemory`][__link28], which can take advantage of memory optimizations. +implements [`HasMemory`][__link39], which can take advantage of memory optimizations. Therefore, it is best to implement this trait on all types that accept byte sequences. -The recommended implementation strategy for [`HasMemory`][__link29] is as follows: +The recommended implementation strategy for [`HasMemory`][__link40] is as follows: -* If your type always passes the data to another type that implements [`HasMemory`][__link30], +* If your type always passes the data to another type that implements [`HasMemory`][__link41], simply forward the memory provider from the other type. * If your type can take advantage of optimizations enabled by specific memory configurations, (e.g. because it uses operating system APIs that unlock better performance when the memory is appropriately configured), return a memory provider that performs the necessary configuration. -* If your type neither passes the data to another type that implements [`HasMemory`][__link31] +* If your type neither passes the data to another type that implements [`HasMemory`][__link42] nor can take advantage of optimizations enabled by specific memory configurations, obtain - an instance of [`GlobalPool`][__link32] as a dependency and return it as the memory provider. + an instance of [`GlobalPool`][__link43] as a dependency and return it as the memory provider. -Example of forwarding the memory provider (see `examples/mem_has_provider_forwarding.rs` +Example of forwarding the memory provider (see `examples/bb_has_memory_forwarding.rs` for full code): ```rust use bytesbuf::{HasMemory, MemoryShared, BytesView}; -/// Counts the number of 0x00 bytes in a sequence before -/// writing that sequence to a network connection. +/// Counts the number of 0x00 bytes in a byte sequence before +/// writing that byte sequence to a network connection. /// /// # Implementation strategy for `HasMemory` /// @@ -306,10 +305,10 @@ impl ConnectionZeroCounter { } } - pub fn write(&mut self, sequence: BytesView) { + pub fn write(&mut self, message: BytesView) { // TODO: Count zeros. - self.connection.write(sequence); + self.connection.write(message); } } @@ -323,7 +322,7 @@ impl HasMemory for ConnectionZeroCounter { ``` Example of returning a memory provider that performs configuration for optimal memory (see -`examples/mem_has_provider_optimizing.rs` for full code): +`examples/bb_has_memory_optimizing.rs` for full code): ```rust use bytesbuf::{CallbackMemory, HasMemory, MemoryShared, BytesView}; @@ -334,7 +333,7 @@ use bytesbuf::{CallbackMemory, HasMemory, MemoryShared, BytesView}; /// the memory is reserved from the I/O memory pool. It uses the I/O context to reserve memory, /// providing a usage-specific configuration when reserving memory capacity. /// -/// A delegating memory provider is used to attach the configuration to each memory reservation. +/// A callback memory provider is used to attach the configuration to each memory reservation. #[derive(Debug)] struct UdpConnection { io_context: IoContext, @@ -368,8 +367,8 @@ impl HasMemory for UdpConnection { ``` -Example of returning a usage-neutral memory provider (see `examples/mem_has_provider_neutral.rs` for -full code): +Example of returning a global memory pool when the type is agnostic toward memory configuration +(see `examples/bb_has_memory_global.rs` for full code): ```rust use bytesbuf::{GlobalPool, HasMemory, MemoryShared}; @@ -386,19 +385,19 @@ use bytesbuf::{GlobalPool, HasMemory, MemoryShared}; #[derive(Debug)] struct ChecksumCalculator { // The application logic must provide this - it is our dependency. - memory_provider: GlobalPool, + memory: GlobalPool, } impl ChecksumCalculator { - pub fn new(memory_provider: GlobalPool) -> Self { - Self { memory_provider } + pub fn new(memory: GlobalPool) -> Self { + Self { memory } } } impl HasMemory for ChecksumCalculator { fn memory(&self) -> impl MemoryShared { - // Cloning a memory provider is a cheap operation, as clones reuse resources. - self.memory_provider.clone() + // Cloning a memory provider is intended to be a cheap operation, reusing resources. + self.memory.clone() } } ``` @@ -416,7 +415,7 @@ Otherwise, the implementation can fall back to a generic implementation that wor byte sequence. Example of identifying whether a byte sequence uses the optimal memory configuration (see -`examples/mem_optimal_path.rs` for full code): +`examples/bb_optimal_path.rs` for full code): ```rust use bytesbuf::BytesView; @@ -426,7 +425,7 @@ pub fn write(&mut self, message: BytesView) { // ues the optimal I/O path. There is no requirement that the data passed to us contains // only memory with our preferred configuration. - let use_optimal_path = message.iter_chunk_metas().all(|meta| { + let use_optimal_path = message.iter_slice_metas().all(|meta| { // If there is no metadata, the memory is not I/O memory. meta.is_some_and(|meta| { // If the type of metadata does not match the metadata @@ -456,15 +455,15 @@ checked for compatibility. ## Compatibility with the `bytes` Crate -The popular [`Bytes`][__link33] type from the `bytes` crate is often used in the Rust ecosystem to +The popular [`Bytes`][__link44] type from the `bytes` crate is often used in the Rust ecosystem to represent simple byte buffers of consecutive bytes. For compatibility with this commonly used -type, this crate offers conversion methods to translate between [`BytesView`][__link34] and [`Bytes`][__link35]: +type, this crate offers conversion methods to translate between [`BytesView`][__link45] and [`Bytes`][__link46]: -* [`BytesView::into_bytes`][__link36] converts a [`BytesView`][__link37] into a [`Bytes`][__link38] instance. This +* [`BytesView::to_bytes()`][__link47] converts a [`BytesView`][__link48] into a [`Bytes`][__link49] instance. This is not always zero-copy because a byte sequence is not guaranteed to be consecutive in memory. You are discouraged from using this method in any performance-relevant logic path. -* `BytesView::from(Bytes)` or `let s: BytesView = bytes.into()` converts a [`Bytes`][__link39] instance - into a [`BytesView`][__link40]. This is an efficient zero-copy operation that reuses the memory of the +* `BytesView::from(Bytes)` or `let s: BytesView = bytes.into()` converts a [`Bytes`][__link50] instance + into a [`BytesView`][__link51]. This is an efficient zero-copy operation that reuses the memory of the `Bytes` instance. ## Static Data @@ -481,7 +480,7 @@ Optimal processing of static data requires satisfying multiple requirements: * We want to use memory that is optimally configured for the context in which the data is consumed (e.g. network connection, file, etc). -The standard pattern here is to use [`OnceLock`][__link41] to lazily initialize a [`BytesView`][__link42] from +The standard pattern here is to use [`OnceLock`][__link52] to lazily initialize a [`BytesView`][__link53] from the static data on first use, using memory from a memory provider that is optimal for the intended usage. @@ -505,8 +504,9 @@ let header_prefix = OnceLock::::new(); for _ in 0..10 { let mut connection = Connection::accept(); - // The static data is transformed into a BytesView on first use, - // using memory optimally configured for a network connection. + // The static data is transformed into a BytesView on first use, using memory optimally configured + // for network connections. The underlying principle is that memory optimally configured for one network + // connection is likely also optimally configured for another network connection, enabling efficient reuse. let header_prefix = header_prefix .get_or_init(|| BytesView::copied_from_slice(HEADER_PREFIX, &connection.memory())); @@ -526,9 +526,9 @@ For testing purposes, this crate exposes some special-purpose memory providers t optimized for real-world usage but may be useful to test corner cases of byte sequence processing in your code: -* [`TransparentTestMemory`][__link43] - a memory provider that does not add any value, just uses memory +* [`TransparentTestMemory`][__link54] - a memory provider that does not add any value, just uses memory from the Rust global allocator. -* [`FixedBlockTestMemory`][__link44] - a variation of the transparent memory provider that limits +* [`FixedBlockTestMemory`][__link55] - a variation of the transparent memory provider that limits each consecutive memory block to a fixed size. This is useful for testing scenarios where you want to ensure that your code works well even if a byte sequence consists of non-consecutive memory. You can go down to as low as 1 byte per block! @@ -539,49 +539,60 @@ processing in your code: This crate was developed as part of The Oxidizer Project. Browse this crate's source code. - [__cargo_doc2readme_dependencies_info]: ggGkYW0CYXSEGy4k8ldDFPOhG2VNeXtD5nnKG6EPY6OfW5wBG8g18NOFNdxpYXKEG5yAxXVX-INyGwACB9YZn6BLG5mUwiQgcGNZGyIa4-qKJXxMYWSBgmhieXRlc2J1ZmUwLjEuMg - [__link0]: https://docs.rs/bytesbuf/0.1.2/bytesbuf/?search=BytesView + [__cargo_doc2readme_dependencies_info]: ggGkYW0CYXSEGy4k8ldDFPOhG2VNeXtD5nnKG6EPY6OfW5wBG8g18NOFNdxpYXKEG3iSI3bi0cZkG8Tg7bBQfy7AG_Go-om_ZeJHG0mzQEby4eVxYWSBgmhieXRlc2J1ZmUwLjEuMg + [__link0]: https://docs.rs/bytesbuf/0.1.2/bytesbuf/?search=BytesBuf [__link1]: https://docs.rs/bytesbuf/0.1.2/bytesbuf/?search=BytesView - [__link10]: https://docs.rs/bytesbuf/0.1.2/bytesbuf/?search=HasMemory - [__link11]: https://docs.rs/bytesbuf/0.1.2/bytesbuf/?search=HasMemory::memory - [__link12]: https://docs.rs/bytesbuf/0.1.2/bytesbuf/?search=GlobalPool - [__link13]: https://docs.rs/bytesbuf/0.1.2/bytesbuf/?search=Memory::reserve - [__link14]: https://docs.rs/bytesbuf/0.1.2/bytesbuf/?search=BytesBuf - [__link15]: https://docs.rs/bytesbuf/0.1.2/bytesbuf/?search=BytesBuf - [__link16]: https://docs.rs/bytes/latest/bytes/buf/trait.BufMut.html - [__link17]: https://docs.rs/bytesbuf/0.1.2/bytesbuf/?search=BytesBuf - [__link18]: https://docs.rs/bytes/latest/bytes/buf/trait.BufMut.html - [__link19]: https://docs.rs/bytes/latest/bytes/buf/trait.BufMut.html#method.chunk_mut + [__link10]: https://docs.rs/bytesbuf/0.1.2/bytesbuf/?search=BytesView::first_slice + [__link11]: https://docs.rs/bytesbuf/0.1.2/bytesbuf/?search=BytesView::first_slice + [__link12]: https://docs.rs/bytesbuf/0.1.2/bytesbuf/?search=BytesView::first_slice + [__link13]: https://docs.rs/bytesbuf/0.1.2/bytesbuf/?search=Memory + [__link14]: https://docs.rs/bytesbuf/0.1.2/bytesbuf/?search=HasMemory + [__link15]: https://docs.rs/bytesbuf/0.1.2/bytesbuf/?search=HasMemory::memory + [__link16]: https://docs.rs/bytesbuf/0.1.2/bytesbuf/?search=GlobalPool + [__link17]: `Memory::reserve()` + [__link18]: https://docs.rs/bytesbuf/0.1.2/bytesbuf/?search=BytesBuf + [__link19]: https://docs.rs/bytesbuf/0.1.2/bytesbuf/?search=BytesBuf [__link2]: https://docs.rs/bytesbuf/0.1.2/bytesbuf/?search=BytesView - [__link20]: https://docs.rs/bytes/latest/bytes/buf/trait.Buf.html#method.advance - [__link21]: https://docs.rs/bytesbuf/0.1.2/bytesbuf/?search=BytesBuf::reserve - [__link22]: https://docs.rs/bytes/latest/bytes/buf/trait.BufMut.html#method.remaining_mut - [__link23]: https://docs.rs/bytesbuf/0.1.2/bytesbuf/?search=BytesView - [__link24]: https://docs.rs/bytesbuf/0.1.2/bytesbuf/?search=BytesView - [__link25]: https://docs.rs/bytesbuf/0.1.2/bytesbuf/?search=BytesBuf - [__link26]: https://docs.rs/bytes/latest/bytes/buf/trait.BufMut.html#method.remaining_mut - [__link27]: https://docs.rs/bytesbuf/0.1.2/bytesbuf/?search=HasMemory - [__link28]: https://docs.rs/bytesbuf/0.1.2/bytesbuf/?search=HasMemory - [__link29]: https://docs.rs/bytesbuf/0.1.2/bytesbuf/?search=HasMemory - [__link3]: https://docs.rs/bytes/latest/bytes/buf/trait.Buf.html - [__link30]: https://docs.rs/bytesbuf/0.1.2/bytesbuf/?search=HasMemory - [__link31]: https://docs.rs/bytesbuf/0.1.2/bytesbuf/?search=HasMemory - [__link32]: https://docs.rs/bytesbuf/0.1.2/bytesbuf/?search=GlobalPool - [__link33]: https://docs.rs/bytes/latest/bytes/struct.Bytes.html + [__link20]: https://docs.rs/bytesbuf/0.1.2/bytesbuf/?search=BytesBuf + [__link21]: https://docs.rs/bytesbuf/0.1.2/bytesbuf/?search=BytesBuf + [__link22]: https://docs.rs/bytesbuf/0.1.2/bytesbuf/?search=BytesBuf::put_num_le + [__link23]: https://docs.rs/bytesbuf/0.1.2/bytesbuf/?search=BytesBuf::put_slice + [__link24]: https://docs.rs/bytesbuf/0.1.2/bytesbuf/?search=BytesBuf::put_byte + [__link25]: https://docs.rs/bytesbuf/0.1.2/bytesbuf/?search=BytesBuf::put_byte_repeated + [__link26]: https://docs.rs/bytesbuf/0.1.2/bytesbuf/?search=BytesBuf::put_bytes + [__link27]: https://docs.rs/bytesbuf/0.1.2/bytesbuf/?search=BytesView + [__link28]: https://docs.rs/bytesbuf/0.1.2/bytesbuf/?search=BytesBuf::first_unfilled_slice + [__link29]: https://docs.rs/bytesbuf/0.1.2/bytesbuf/?search=BytesBuf::advance + [__link3]: https://docs.rs/bytesbuf/0.1.2/bytesbuf/?search=BytesView::get_num_le + [__link30]: https://docs.rs/bytesbuf/0.1.2/bytesbuf/?search=BytesBuf::first_unfilled_slice + [__link31]: https://docs.rs/bytesbuf/0.1.2/bytesbuf/?search=BytesBuf::first_unfilled_slice + [__link32]: `BytesBuf::reserve()` + [__link33]: https://docs.rs/bytesbuf/0.1.2/bytesbuf/?search=BytesBuf::remaining_capacity [__link34]: https://docs.rs/bytesbuf/0.1.2/bytesbuf/?search=BytesView - [__link35]: https://docs.rs/bytes/latest/bytes/struct.Bytes.html - [__link36]: https://docs.rs/bytesbuf/0.1.2/bytesbuf/?search=BytesView::into_bytes - [__link37]: https://docs.rs/bytesbuf/0.1.2/bytesbuf/?search=BytesView - [__link38]: https://docs.rs/bytes/latest/bytes/struct.Bytes.html - [__link39]: https://docs.rs/bytes/latest/bytes/struct.Bytes.html - [__link4]: https://docs.rs/bytesbuf/0.1.2/bytesbuf/?search=BytesView - [__link40]: https://docs.rs/bytesbuf/0.1.2/bytesbuf/?search=BytesView - [__link41]: https://doc.rust-lang.org/stable/std/?search=sync::OnceLock - [__link42]: https://docs.rs/bytesbuf/0.1.2/bytesbuf/?search=BytesView - [__link43]: https://docs.rs/bytesbuf/0.1.2/bytesbuf/?search=TransparentTestMemory - [__link44]: https://docs.rs/bytesbuf/0.1.2/bytesbuf/?search=FixedBlockTestMemory - [__link5]: https://docs.rs/bytes/latest/bytes/buf/trait.Buf.html - [__link6]: https://docs.rs/bytes/latest/bytes/buf/trait.Buf.html#method.chunk - [__link7]: https://docs.rs/bytes/latest/bytes/buf/trait.Buf.html#method.advance - [__link8]: https://docs.rs/bytes/latest/bytes/buf/trait.Buf.html#method.chunks_vectored - [__link9]: https://docs.rs/bytesbuf/0.1.2/bytesbuf/?search=Memory + [__link35]: https://docs.rs/bytesbuf/0.1.2/bytesbuf/?search=BytesView + [__link36]: https://docs.rs/bytesbuf/0.1.2/bytesbuf/?search=BytesBuf + [__link37]: `BytesBuf::put_bytes()` + [__link38]: https://docs.rs/bytesbuf/0.1.2/bytesbuf/?search=HasMemory + [__link39]: https://docs.rs/bytesbuf/0.1.2/bytesbuf/?search=HasMemory + [__link4]: https://docs.rs/bytesbuf/0.1.2/bytesbuf/?search=BytesView::get_byte + [__link40]: https://docs.rs/bytesbuf/0.1.2/bytesbuf/?search=HasMemory + [__link41]: https://docs.rs/bytesbuf/0.1.2/bytesbuf/?search=HasMemory + [__link42]: https://docs.rs/bytesbuf/0.1.2/bytesbuf/?search=HasMemory + [__link43]: https://docs.rs/bytesbuf/0.1.2/bytesbuf/?search=GlobalPool + [__link44]: https://docs.rs/bytes/latest/bytes/struct.Bytes.html + [__link45]: https://docs.rs/bytesbuf/0.1.2/bytesbuf/?search=BytesView + [__link46]: https://docs.rs/bytes/latest/bytes/struct.Bytes.html + [__link47]: https://docs.rs/bytesbuf/0.1.2/bytesbuf/?search=BytesView::to_bytes + [__link48]: https://docs.rs/bytesbuf/0.1.2/bytesbuf/?search=BytesView + [__link49]: https://docs.rs/bytes/latest/bytes/struct.Bytes.html + [__link5]: https://docs.rs/bytesbuf/0.1.2/bytesbuf/?search=BytesView::copy_to_slice + [__link50]: https://docs.rs/bytes/latest/bytes/struct.Bytes.html + [__link51]: https://docs.rs/bytesbuf/0.1.2/bytesbuf/?search=BytesView + [__link52]: https://doc.rust-lang.org/stable/std/?search=sync::OnceLock + [__link53]: https://docs.rs/bytesbuf/0.1.2/bytesbuf/?search=BytesView + [__link54]: https://docs.rs/bytesbuf/0.1.2/bytesbuf/?search=TransparentTestMemory + [__link55]: https://docs.rs/bytesbuf/0.1.2/bytesbuf/?search=FixedBlockTestMemory + [__link6]: https://docs.rs/bytesbuf/0.1.2/bytesbuf/?search=BytesView::copy_to_uninit_slice + [__link7]: https://docs.rs/bytesbuf/0.1.2/bytesbuf/?search=BytesView::as_read + [__link8]: https://docs.rs/bytesbuf/0.1.2/bytesbuf/?search=BytesView::first_slice + [__link9]: https://docs.rs/bytesbuf/0.1.2/bytesbuf/?search=BytesView::advance diff --git a/crates/bytesbuf/benches/buf.rs b/crates/bytesbuf/benches/buf.rs index 6ea636fb..748bbf53 100644 --- a/crates/bytesbuf/benches/buf.rs +++ b/crates/bytesbuf/benches/buf.rs @@ -9,8 +9,7 @@ use std::iter; use std::num::NonZero; use alloc_tracker::{Allocator, Session}; -use bytes::{Buf, BufMut}; -use bytesbuf::{BlockSize, BytesBuf, BytesView, FixedBlockTestMemory}; +use bytesbuf::{BlockSize, BytesBuf, BytesView, FixedBlockTestMemory, TransparentTestMemory}; use criterion::{BatchSize, Criterion, criterion_group, criterion_main}; use new_zealand::nz; @@ -28,20 +27,22 @@ const TEST_DATA: &[u8] = &[88_u8; TEST_SPAN_SIZE.get() as usize]; const MAX_INLINE_SPANS: usize = bytesbuf::MAX_INLINE_SPANS; // This should be more than MAX_INLINE_SPANS. const MANY_SPANS: usize = 32; +const PUT_BYTES_LEN: usize = 512; #[expect(clippy::too_many_lines, reason = "Is fine - lots of benchmarks to do!")] fn entrypoint(c: &mut Criterion) { let allocs = Session::new(); let memory = FixedBlockTestMemory::new(TEST_SPAN_SIZE); + let transparent_memory = TransparentTestMemory::new(); let test_data_as_seq = BytesView::copied_from_slice(TEST_DATA, &memory); let max_inline = iter::repeat_n(test_data_as_seq.clone(), MAX_INLINE_SPANS).collect::>(); - let max_inline_as_seq = BytesView::from_sequences(max_inline.iter().cloned()); + let max_inline_as_seq = BytesView::from_views(max_inline.iter().cloned()); let many = iter::repeat_n(test_data_as_seq.clone(), MANY_SPANS).collect::>(); - let many_as_seq = BytesView::from_sequences(many.iter().cloned()); + let many_as_seq = BytesView::from_views(many.iter().cloned()); let mut group = c.benchmark_group("BytesBuf"); @@ -61,7 +62,7 @@ fn entrypoint(c: &mut Criterion) { b.iter_batched_ref( || { let mut sb = BytesBuf::new(); - sb.append(many_as_seq.clone()); + sb.put_bytes(many_as_seq.clone()); sb }, |sb| sb.len(), @@ -77,7 +78,7 @@ fn entrypoint(c: &mut Criterion) { b.iter_batched_ref( || { let mut sb = BytesBuf::new(); - sb.append(many_as_seq.clone()); + sb.put_bytes(many_as_seq.clone()); sb }, |sb| sb.is_empty(), @@ -93,7 +94,7 @@ fn entrypoint(c: &mut Criterion) { b.iter_batched_ref( || { let mut sb = BytesBuf::new(); - sb.append(many_as_seq.clone()); + sb.put_bytes(many_as_seq.clone()); sb }, |sb| sb.capacity(), @@ -105,30 +106,142 @@ fn entrypoint(c: &mut Criterion) { b.iter_batched_ref(BytesBuf::new, |sb| sb.reserve(black_box(1), &memory), BatchSize::SmallInput); }); - let allocs_op = allocs.operation("append_clean"); - group.bench_function("append_clean", |b| { + let allocs_op = allocs.operation("put_f64_be"); + group.bench_function("put_f64_be", |b| { + b.iter_batched_ref( + || { + let mut sb = BytesBuf::new(); + sb.reserve(std::mem::size_of::(), &transparent_memory); + sb + }, + |sb| { + let _span = allocs_op.measure_thread(); + sb.put_num_be::(black_box(1234.5678)); + }, + BatchSize::SmallInput, + ); + }); + + let allocs_op = allocs.operation("put_u64_be"); + group.bench_function("put_u64_be", |b| { + b.iter_batched_ref( + || { + let mut sb = BytesBuf::new(); + sb.reserve(std::mem::size_of::(), &transparent_memory); + sb + }, + |sb| { + let _span = allocs_op.measure_thread(); + sb.put_num_be::(black_box(0x1234_5678_9ABC_DEF0)); + }, + BatchSize::SmallInput, + ); + }); + + let allocs_op = allocs.operation("put_f64_le"); + group.bench_function("put_f64_le", |b| { + b.iter_batched_ref( + || { + let mut sb = BytesBuf::new(); + sb.reserve(std::mem::size_of::(), &transparent_memory); + sb + }, + |sb| { + let _span = allocs_op.measure_thread(); + sb.put_num_le::(black_box(8765.4321)); + }, + BatchSize::SmallInput, + ); + }); + + let allocs_op = allocs.operation("put_u64_le"); + group.bench_function("put_u64_le", |b| { + b.iter_batched_ref( + || { + let mut sb = BytesBuf::new(); + sb.reserve(std::mem::size_of::(), &transparent_memory); + sb + }, + |sb| { + let _span = allocs_op.measure_thread(); + sb.put_num_le::(black_box(0x0FED_CBA9_8765_4321)); + }, + BatchSize::SmallInput, + ); + }); + + let allocs_op = allocs.operation("put_u8"); + group.bench_function("put_u8", |b| { + b.iter_batched_ref( + || { + let mut sb = BytesBuf::new(); + sb.reserve(1, &transparent_memory); + sb + }, + |sb| { + let _span = allocs_op.measure_thread(); + sb.put_num_le::(black_box(0xAB)); + }, + BatchSize::SmallInput, + ); + }); + + let allocs_op = allocs.operation("put_bytes"); + group.bench_function("put_bytes", |b| { + b.iter_batched_ref( + || { + let mut sb = BytesBuf::new(); + sb.reserve(PUT_BYTES_LEN, &transparent_memory); + sb + }, + |sb| { + let _span = allocs_op.measure_thread(); + sb.put_byte_repeated(0xCD, PUT_BYTES_LEN); + }, + BatchSize::SmallInput, + ); + }); + + let allocs_op = allocs.operation("put_buf"); + group.bench_function("put", |b| { + b.iter_batched_ref( + || { + let mut sb = BytesBuf::new(); + sb.reserve(test_data_as_seq.len(), &memory); + sb + }, + |sb| { + let _span = allocs_op.measure_thread(); + sb.put_bytes(test_data_as_seq.clone()); + }, + BatchSize::SmallInput, + ); + }); + + let allocs_op = allocs.operation("put_view_clean"); + group.bench_function("put_view_clean", |b| { b.iter_batched_ref( BytesBuf::new, |sb| { let _span = allocs_op.measure_thread(); - sb.append(test_data_as_seq.clone()); + sb.put_bytes(test_data_as_seq.clone()); }, BatchSize::SmallInput, ); }); - let allocs_op = allocs.operation("append_dirty"); - group.bench_function("append_dirty", |b| { + let allocs_op = allocs.operation("put_view_dirty"); + group.bench_function("put_view_dirty", |b| { b.iter_batched_ref( || { let mut sb = BytesBuf::new(); sb.reserve(TEST_SPAN_SIZE.get() as usize, &memory); - sb.put_u8(123); + sb.put_byte(123); sb }, |sb| { let _span = allocs_op.measure_thread(); - sb.append(test_data_as_seq.clone()); + sb.put_bytes(test_data_as_seq.clone()); }, BatchSize::SmallInput, ); @@ -139,7 +252,7 @@ fn entrypoint(c: &mut Criterion) { b.iter_batched_ref( || { let mut sb = BytesBuf::new(); - sb.append(many_as_seq.clone()); + sb.put_bytes(many_as_seq.clone()); sb }, |sb| { @@ -155,7 +268,7 @@ fn entrypoint(c: &mut Criterion) { b.iter_batched_ref( || { let mut sb = BytesBuf::new(); - sb.append(max_inline_as_seq.clone()); + sb.put_bytes(max_inline_as_seq.clone()); sb }, |sb| { @@ -171,7 +284,7 @@ fn entrypoint(c: &mut Criterion) { b.iter_batched_ref( || { let mut sb = BytesBuf::new(); - sb.append(many_as_seq.clone()); + sb.put_bytes(many_as_seq.clone()); sb }, |sb| { @@ -187,7 +300,7 @@ fn entrypoint(c: &mut Criterion) { b.iter_batched_ref( || { let mut sb = BytesBuf::new(); - sb.append(test_data_as_seq.clone()); + sb.put_bytes(test_data_as_seq.clone()); sb }, |sb| { @@ -289,7 +402,7 @@ fn entrypoint(c: &mut Criterion) { // SAFETY: Yes, I promise I wrote this many bytes. // This is a lie but we do not touch the bytes, so should be a harmless lie. unsafe { - sb.advance_mut(BLOCK_SIZE.get() as usize); + sb.advance(BLOCK_SIZE.get() as usize); } }, BatchSize::SmallInput, @@ -301,7 +414,7 @@ fn entrypoint(c: &mut Criterion) { b.iter_batched_ref( || { let mut sb = BytesBuf::new(); - sb.append(many_as_seq.clone()); + sb.put_bytes(many_as_seq.clone()); sb }, |sb| { @@ -309,8 +422,8 @@ fn entrypoint(c: &mut Criterion) { let mut peeked = sb.peek(); // We just seek to the end, that is all. - while peeked.has_remaining() { - peeked.advance(peeked.chunk().len()); + while !peeked.is_empty() { + peeked.advance(peeked.first_slice().len()); } }, BatchSize::SmallInput, @@ -323,7 +436,7 @@ fn entrypoint(c: &mut Criterion) { || { let mut sb = BytesBuf::new(); sb.reserve(TEST_SPAN_SIZE.get() as usize, &memory); - sb.put_u8(123); + sb.put_byte(123); sb }, |sb| { @@ -331,8 +444,8 @@ fn entrypoint(c: &mut Criterion) { let mut peeked = sb.peek(); // We just seek to the end, that is all. - while peeked.has_remaining() { - peeked.advance(peeked.chunk().len()); + while !peeked.is_empty() { + peeked.advance(peeked.first_slice().len()); } }, BatchSize::SmallInput, diff --git a/crates/bytesbuf/benches/global_pool.rs b/crates/bytesbuf/benches/global_pool.rs index 60d396c2..62624644 100644 --- a/crates/bytesbuf/benches/global_pool.rs +++ b/crates/bytesbuf/benches/global_pool.rs @@ -6,7 +6,6 @@ use std::alloc::System; use alloc_tracker::{Allocator, Session}; -use bytes::BufMut; use bytesbuf::{BytesView, GlobalPool}; use criterion::{BatchSize, Criterion, criterion_group, criterion_main}; use testing_aids::repeating_incrementing_bytes; @@ -34,7 +33,7 @@ fn entrypoint(c: &mut Criterion) { b.iter(|| { let _span = allocs_op.measure_thread(); let mut sb = warm_memory.reserve(ONE_MB); - sb.put_bytes(66, ONE_MB); + sb.put_byte_repeated(66, ONE_MB); }); }); @@ -45,7 +44,7 @@ fn entrypoint(c: &mut Criterion) { |memory| { let _span = allocs_op.measure_thread(); let mut sb = memory.reserve(ONE_MB); - sb.put_bytes(66, ONE_MB); + sb.put_byte_repeated(66, ONE_MB); }, BatchSize::LargeInput, ); diff --git a/crates/bytesbuf/benches/view.rs b/crates/bytesbuf/benches/view.rs index cf3a2ad0..149c3b63 100644 --- a/crates/bytesbuf/benches/view.rs +++ b/crates/bytesbuf/benches/view.rs @@ -9,7 +9,6 @@ use std::iter; use std::num::NonZero; use alloc_tracker::{Allocator, Session}; -use bytes::Buf; use bytesbuf::{BlockSize, BytesView, FixedBlockTestMemory}; use criterion::{BatchSize, Criterion, criterion_group, criterion_main}; use new_zealand::nz; @@ -28,6 +27,7 @@ const TEST_DATA: &[u8] = &[88_u8; TEST_SPAN_SIZE.get() as usize]; const MAX_INLINE_SPANS: usize = bytesbuf::MAX_INLINE_SPANS; // This should be more than MAX_INLINE_SPANS. const MANY_SPANS: usize = 32; +const COPY_TO_SLICE_LEN: usize = 256; #[expect(clippy::too_many_lines, reason = "Is fine - lots of benchmarks to do!")] fn entrypoint(c: &mut Criterion) { @@ -40,11 +40,11 @@ fn entrypoint(c: &mut Criterion) { let max_inline = iter::repeat_n(test_data_as_seq.clone(), MAX_INLINE_SPANS).collect::>(); let many = iter::repeat_n(test_data_as_seq.clone(), MANY_SPANS).collect::>(); - let many_as_seq = BytesView::from_sequences(many.iter().cloned()); - let many_as_bytes = many_as_seq.clone().into_bytes(); + let many_as_seq = BytesView::from_views(many.iter().cloned()); + let many_as_vec = many_as_seq.to_bytes().to_vec(); let ten = iter::repeat_n(test_data_as_seq.clone(), 10).collect::>(); - let ten_as_seq = BytesView::from_sequences(ten.iter().cloned()); + let ten_as_seq = BytesView::from_views(ten.iter().cloned()); let mut group = c.benchmark_group("BytesView"); @@ -88,7 +88,7 @@ fn entrypoint(c: &mut Criterion) { group.bench_function("slice_near", |b| { b.iter(|| { let _span = allocs_op.measure_thread(); - test_data_as_seq.slice(black_box(0..10)) + test_data_as_seq.range(black_box(0..10)) }); }); @@ -96,7 +96,7 @@ fn entrypoint(c: &mut Criterion) { group.bench_function("slice_far", |b| { b.iter(|| { let _span = allocs_op.measure_thread(); - test_data_as_seq.slice(black_box(12300..12310)) + test_data_as_seq.range(black_box(12300..12310)) }); }); @@ -105,7 +105,7 @@ fn entrypoint(c: &mut Criterion) { // There are 10 spans in this sequence, with our slice being from the last one. b.iter(|| { let _span = allocs_op.measure_thread(); - ten_as_seq.slice(black_box(123_000..123_010)) + ten_as_seq.range(black_box(123_000..123_010)) }); }); @@ -115,7 +115,7 @@ fn entrypoint(c: &mut Criterion) { || test_data_as_seq.clone(), |seq| { let _span = allocs_op.measure_thread(); - seq.consume_all_chunks(|chunk| { + seq.consume_all_slices(|chunk| { _ = black_box(chunk); }); }, @@ -127,7 +127,7 @@ fn entrypoint(c: &mut Criterion) { b.iter(|| { // Will only fill 1 of 4 slots, since the test data is just one chunk. let mut slices: Vec<&[u8]> = vec![&[]; 4]; - test_data_as_seq.chunks_as_slices_vectored(&mut slices); + test_data_as_seq.slices(&mut slices); _ = black_box(slices); }); @@ -163,13 +163,76 @@ fn entrypoint(c: &mut Criterion) { ); }); - let allocs_op = allocs.operation("to_bytes_single_chunk"); - group.bench_function("to_bytes_single_chunk", |b| { - let seq = BytesView::from(test_data_as_seq.clone().into_bytes()); + group.bench_function("get_f64_be", |b| { + b.iter_batched_ref( + || many_as_seq.clone(), + |seq| { + black_box(seq.get_num_be::()); + }, + BatchSize::SmallInput, + ); + }); + + group.bench_function("get_u64_be", |b| { + b.iter_batched_ref( + || many_as_seq.clone(), + |seq| { + black_box(seq.get_num_be::()); + }, + BatchSize::SmallInput, + ); + }); + + group.bench_function("get_f64_le", |b| { + b.iter_batched_ref( + || many_as_seq.clone(), + |seq| { + black_box(seq.get_num_le::()); + }, + BatchSize::SmallInput, + ); + }); + + group.bench_function("get_u64_le", |b| { + b.iter_batched_ref( + || many_as_seq.clone(), + |seq| { + black_box(seq.get_num_le::()); + }, + BatchSize::SmallInput, + ); + }); + + group.bench_function("get_u8", |b| { + b.iter_batched_ref( + || many_as_seq.clone(), + |seq| { + black_box(seq.get_num_le::()); + }, + BatchSize::SmallInput, + ); + }); + + group.bench_function("copy_to_slice", |b| { + b.iter_batched_ref( + || many_as_seq.clone(), + |seq| { + let mut target = [0u8; COPY_TO_SLICE_LEN]; + seq.copy_to_slice(&mut target); + black_box(target); + }, + BatchSize::SmallInput, + ); + }); + + let allocs_op = allocs.operation("to_bytes_single_span"); + group.bench_function("to_bytes_single_span", |b| { + // This forces it to be a single span. + let view = BytesView::from(test_data_as_seq.to_bytes()); b.iter(|| { let _span = allocs_op.measure_process(); - let _bytes = seq.clone().into_bytes(); + let _bytes = view.to_bytes(); }); }); @@ -189,7 +252,7 @@ fn entrypoint(c: &mut Criterion) { group.bench_function("eq_slice", |b| { b.iter_batched_ref( - || many_as_bytes.chunk(), + || many_as_vec.as_slice(), |other| { assert!(black_box(many_as_seq == *other)); }, @@ -197,11 +260,11 @@ fn entrypoint(c: &mut Criterion) { ); }); - let allocs_op = allocs.operation("to_bytes_many_chunks"); - group.bench_function("to_bytes_many_chunks", |b| { + let allocs_op = allocs.operation("to_bytes_many_spans"); + group.bench_function("to_bytes_many_spans", |b| { b.iter(|| { let _span = allocs_op.measure_process(); - let _bytes = many_as_seq.clone().into_bytes(); + let _bytes = many_as_seq.to_bytes(); }); }); @@ -211,7 +274,7 @@ fn entrypoint(c: &mut Criterion) { || many.iter().cloned(), |many_clones| { let _span = allocs_op.measure_thread(); - BytesView::from_sequences(black_box(many_clones)) + BytesView::from_views(black_box(many_clones)) }, BatchSize::SmallInput, ); @@ -231,7 +294,7 @@ fn entrypoint(c: &mut Criterion) { || max_inline.iter().cloned(), |max_inline_clones| { let _span = allocs_op.measure_thread(); - BytesView::from_sequences(black_box(max_inline_clones)) + BytesView::from_views(black_box(max_inline_clones)) }, BatchSize::SmallInput, ); diff --git a/crates/bytesbuf/doc/snippets/choosing_memory_provider.md b/crates/bytesbuf/doc/snippets/choosing_memory_provider.md index 8777c7fb..e32e4baf 100644 --- a/crates/bytesbuf/doc/snippets/choosing_memory_provider.md +++ b/crates/bytesbuf/doc/snippets/choosing_memory_provider.md @@ -5,5 +5,5 @@ If you are writing bytes to or reading bytes from an object that either itself i you should use [`Memory::reserve()`][crate::Memory::reserve] from this provider to obtain memory to store bytes in. -Otherwise, use [`GlobalPool`][crate::GlobalPool], which is a reasonable +Otherwise, use a shared instance of [`GlobalPool`][crate::GlobalPool], which is a reasonable default when there is no specific reason use a different memory provider. diff --git a/crates/bytesbuf/doc/snippets/sequence_memory_layout.md b/crates/bytesbuf/doc/snippets/sequence_memory_layout.md index 02c4eaa6..14fcc5c7 100644 --- a/crates/bytesbuf/doc/snippets/sequence_memory_layout.md +++ b/crates/bytesbuf/doc/snippets/sequence_memory_layout.md @@ -1,14 +1,17 @@ # Memory layout -A byte sequence backed by I/O memory may consist of any number of spans of consecutive bytes. +A byte sequence represented by `bytesbuf` types may consist of any number +of separate byte slices, each of which contains one or more bytes of data. -There is no upper or lower bound on the length of each span of bytes. At one extreme, the I/O -subsystem may allocate a single span of memory to hold all the data. At the opposite extreme, it is -legal for the I/O subsystem to create byte where byte is stored as a separate allocation. -Higher level APIs are required not assume any specific block size +There is no upper or lower bound on the length of each slice of bytes. At one extreme, +a byte sequence may be entirely represented by a single slice of bytes. At the opposite +extreme, it is legal for each byte to be represented by a separate non-consecutive slice. -Examples of how `b'Hello'` may be stored in I/O memory: +Examples of legal memory layouts for the byte sequence `b'Hello'`: * `['H', 'e', 'l', 'l', 'o']` * `['H', 'e'], ['l', 'l', 'o']` * `['H'], ['e'], ['l'], ['l'], ['o']` + +Code using these APIs is required to work with any memory layout, as there are +no guarantees on which layout will be used for which byte sequences. diff --git a/crates/bytesbuf/examples/mem_basic.rs b/crates/bytesbuf/examples/bb_basic.rs similarity index 55% rename from crates/bytesbuf/examples/mem_basic.rs rename to crates/bytesbuf/examples/bb_basic.rs index dddb3704..bffc8425 100644 --- a/crates/bytesbuf/examples/mem_basic.rs +++ b/crates/bytesbuf/examples/bb_basic.rs @@ -1,14 +1,16 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -//! Basics of working with byte sequences. We obtain some memory, encode a message into it, -//! and then receive this message in another function and write a status report to the terminal. +//! Basics of working with `BytesBuf` and `BytesView`. +//! +//! 1. We creates a `BytesBuf` with some memory capacity. +//! 2. We encode a message into this buffer and create a `BytesView` over the message. +//! 3. We receive the message in another function and write a status report to the terminal. -use bytes::{Buf, BufMut}; use bytesbuf::{BytesView, GlobalPool, Memory}; fn main() { - // The global memory pool in a real application would be provided by the framework. + // The global memory pool in real-world code would be provided by the application framework. let memory = GlobalPool::new(); let message = produce_message(&memory); @@ -23,30 +25,28 @@ fn produce_message(memory: &impl Memory) -> BytesView { const MESSAGE_LEN_BYTES: usize = MESSAGE_LEN_WORDS * size_of::(); // Reserve enough memory for the message. The memory provider may provide more than requested. - let mut sequence_builder = memory.reserve(MESSAGE_LEN_BYTES); + let mut buf = memory.reserve(MESSAGE_LEN_BYTES); println!( "Requested {MESSAGE_LEN_BYTES} bytes of memory capacity, got {} bytes.", - sequence_builder.capacity() + buf.capacity() ); - // Each word is just an incrementing number, starting from 0. + // Each word is just an incrementing binary-serialized number, starting from 0. (0..MESSAGE_LEN_WORDS).for_each(|word| { - sequence_builder.put_u64(word as u64); + buf.put_num_le(word as u64); }); - // Detaches a sequence of immutable bytes from the builder, consisting of all the data - // written into it so far. The builder remains usable for further writes into any remaining - // capacity, although we do not make use of that functionality here. - sequence_builder.consume_all() + // Creates a BytesView over all the data written into the BytesBuf. + buf.consume_all() } fn consume_message(mut message: BytesView) { // We read the message and calculate the sum of all the words in it. let mut sum: u64 = 0; - while message.has_remaining() { - let word = message.get_u64(); + while !message.is_empty() { + let word = message.get_num_le::(); sum = sum.saturating_add(word); } diff --git a/crates/bytesbuf/examples/mem_has_memory_forwarding.rs b/crates/bytesbuf/examples/bb_has_memory_forwarding.rs similarity index 86% rename from crates/bytesbuf/examples/mem_has_memory_forwarding.rs rename to crates/bytesbuf/examples/bb_has_memory_forwarding.rs index a3131a34..9df73220 100644 --- a/crates/bytesbuf/examples/mem_has_memory_forwarding.rs +++ b/crates/bytesbuf/examples/bb_has_memory_forwarding.rs @@ -4,7 +4,6 @@ //! Showcases how to implement the `HasMemory` trait using the forwarding //! implementation strategy, whereby the memory provider of a dependency is used. -use bytes::{Buf, BufMut}; use bytesbuf::{BytesBuf, BytesView, HasMemory, Memory, MemoryShared, TransparentTestMemory}; const PAYLOAD_LEN: usize = 12345; @@ -21,20 +20,20 @@ fn main() { } fn create_payload(memory: &impl Memory) -> BytesView { - let mut sequence_builder = memory.reserve(PAYLOAD_LEN); + let mut buf = memory.reserve(PAYLOAD_LEN); // We write PAYLOAD_LEN bytes, incrementing the value // by 1 each time, so we get [0, 1, 2, 3, ...]. for i in 0..PAYLOAD_LEN { #[expect(clippy::cast_possible_truncation, reason = "intentional")] - sequence_builder.put_u8(i as u8); + buf.put_byte(i as u8); } - sequence_builder.consume_all() + buf.consume_all() } -/// Counts the number of 0x00 bytes in a sequence before -/// writing that sequence to a network connection. +/// Counts the number of 0x00 bytes in a byte sequence before +/// writing that byte sequence to a network connection. /// /// # Implementation strategy for `HasMemory` /// @@ -56,16 +55,16 @@ impl ConnectionZeroCounter { Self { connection, zero_count: 0 } } - pub fn write(&mut self, sequence: BytesView) { + pub fn write(&mut self, message: BytesView) { // Cloning a BytesView is a cheap zero-copy operation, - self.count_zeros(sequence.clone()); + self.count_zeros(message.clone()); - self.connection.write(sequence); + self.connection.write(message); } - fn count_zeros(&mut self, mut sequence: BytesView) { - while sequence.has_remaining() { - if sequence.get_u8() == 0 { + fn count_zeros(&mut self, mut message: BytesView) { + while !message.is_empty() { + if message.get_byte() == 0 { self.zero_count = self.zero_count.wrapping_add(1); } } diff --git a/crates/bytesbuf/examples/mem_has_memory_neutral.rs b/crates/bytesbuf/examples/bb_has_memory_global.rs similarity index 64% rename from crates/bytesbuf/examples/mem_has_memory_neutral.rs rename to crates/bytesbuf/examples/bb_has_memory_global.rs index 1210855b..64c61198 100644 --- a/crates/bytesbuf/examples/mem_has_memory_neutral.rs +++ b/crates/bytesbuf/examples/bb_has_memory_global.rs @@ -4,33 +4,32 @@ //! Showcases how to implement the `HasMemory` trait //! using the `GlobalPool` implementation strategy. -use bytes::Buf; use bytesbuf::{BytesView, GlobalPool, HasMemory, MemoryShared}; fn main() { - // The global memory pool in a real application would be provided by the framework. + // The global memory pool in real-world code would be provided by the application framework. let global_memory_pool = GlobalPool::new(); let mut checksum_calculator = ChecksumCalculator::new(global_memory_pool); // When we obtain an instance of a type that implements `HasMemory`, - // we should extract the memory provider so we can reuse it across calls to the instance. + // we should extract the memory provider if we need to access it on demand. let memory = checksum_calculator.memory(); - // These byte sequences are meant to be passed to the checksum calculator, + // These messages are meant to be passed to the checksum calculator, // so they use the memory provider we obtained from the checksum calculator. - let data1 = BytesView::copied_from_slice(b"Hello, world!", &memory); - let data2 = BytesView::copied_from_slice(b"Goodbye, world!", &memory); - let data3 = BytesView::copied_from_slice(b"Goodbye, universe!", &memory); + let message1 = BytesView::copied_from_slice(b"Hello, world!", &memory); + let message2 = BytesView::copied_from_slice(b"Goodbye, world!", &memory); + let message3 = BytesView::copied_from_slice(b"Goodbye, universe!", &memory); - checksum_calculator.add_bytes(data1); - checksum_calculator.add_bytes(data2); - checksum_calculator.add_bytes(data3); + checksum_calculator.add_bytes(message1); + checksum_calculator.add_bytes(message2); + checksum_calculator.add_bytes(message3); println!("Checksum: {}", checksum_calculator.checksum()); } -/// Calculates a checksum for a given byte sequence. +/// Calculates a checksum for a given message. /// /// # Implementation strategy for `HasMemory` /// @@ -42,22 +41,19 @@ fn main() { #[derive(Debug)] struct ChecksumCalculator { // The application logic must provide this - it is our dependency. - memory_provider: GlobalPool, + memory: GlobalPool, checksum: u64, } impl ChecksumCalculator { - pub const fn new(memory_provider: GlobalPool) -> Self { - Self { - memory_provider, - checksum: 0, - } + pub const fn new(memory: GlobalPool) -> Self { + Self { memory, checksum: 0 } } pub fn add_bytes(&mut self, mut bytes: BytesView) { while !bytes.is_empty() { - let b = bytes.get_u8(); + let b = bytes.get_byte(); self.checksum = self.checksum.wrapping_add(u64::from(b)); } } @@ -70,6 +66,6 @@ impl ChecksumCalculator { impl HasMemory for ChecksumCalculator { fn memory(&self) -> impl MemoryShared { // Cloning a memory provider is intended to be a cheap operation, reusing resources. - self.memory_provider.clone() + self.memory.clone() } } diff --git a/crates/bytesbuf/examples/mem_has_memory_optimizing.rs b/crates/bytesbuf/examples/bb_has_memory_optimizing.rs similarity index 91% rename from crates/bytesbuf/examples/mem_has_memory_optimizing.rs rename to crates/bytesbuf/examples/bb_has_memory_optimizing.rs index e36094f4..10548b15 100644 --- a/crates/bytesbuf/examples/mem_has_memory_optimizing.rs +++ b/crates/bytesbuf/examples/bb_has_memory_optimizing.rs @@ -5,7 +5,6 @@ //! strategy that obtains memory from a memory provider specific to a particular purpose, with //! a configuration optimal for that purpose. -use bytes::BufMut; use bytesbuf::{BytesBuf, BytesView, CallbackMemory, HasMemory, Memory, MemoryShared, TransparentTestMemory}; fn main() { @@ -15,12 +14,12 @@ fn main() { let mut connection = UdpConnection::new(io_context); // Prepare a packet to send and send it. - let mut sequence_builder = connection.memory().reserve(1 + 8 + 16); - sequence_builder.put_u8(42); - sequence_builder.put_u64(43); - sequence_builder.put_u128(44); + let mut buf = connection.memory().reserve(1 + 8 + 16); + buf.put_byte(42); + buf.put_num_be(43_u64); + buf.put_num_be(44_u128); - let packet = sequence_builder.consume_all(); + let packet = buf.consume_all(); connection.write(packet); } @@ -51,7 +50,7 @@ impl UdpConnection { pub fn write(&mut self, packet: BytesView) { // Note: making use of optimally configured memory may need some additional logic here. // This is out of scope of this example, because this example targets targeting how to - // implement HasMemory. See `mem_optimal_path.rs` for an example of a type that + // implement HasMemory. See `bb_optimal_path.rs` for an example of a type that // has both an "optimal" and a "fallback" implementation depending on memory used. println!("Sending packet of length: {}", packet.len()); } diff --git a/crates/bytesbuf/examples/mem_optimal_path.rs b/crates/bytesbuf/examples/bb_optimal_path.rs similarity index 98% rename from crates/bytesbuf/examples/mem_optimal_path.rs rename to crates/bytesbuf/examples/bb_optimal_path.rs index a8210a53..1f7d870a 100644 --- a/crates/bytesbuf/examples/mem_optimal_path.rs +++ b/crates/bytesbuf/examples/bb_optimal_path.rs @@ -18,7 +18,7 @@ use std::num::NonZero; use bytesbuf::{BlockSize, BytesBuf, BytesView, CallbackMemory, GlobalPool, HasMemory, MemoryShared}; fn main() { - // In a real application, both of these would be provided by the framework. + // In real-world code, both of these would be provided by the application framework. let global_memory_pool = GlobalPool::new(); let io_context = IoContext::new(); @@ -35,7 +35,7 @@ fn main() { // This message uses a combination of both memory providers. This will not use the optimal // I/O path because the optimal I/O path requires all memory in a byte sequence to be optimal. - let message3 = BytesView::from_sequences([message1, message2]); + let message3 = BytesView::from_views([message1, message2]); connection.write(message3); } @@ -64,7 +64,7 @@ impl Connection { // ues the optimal I/O path. There is no requirement that the data passed to us contains // only memory with our preferred configuration. - let use_optimal_path = message.iter_chunk_metas().all(|meta| { + let use_optimal_path = message.iter_slice_metas().all(|meta| { // If there is no metadata, the memory is not I/O memory. meta.is_some_and(|meta| { // If the type of metadata does not match the metadata diff --git a/crates/bytesbuf/examples/bb_reuse.rs b/crates/bytesbuf/examples/bb_reuse.rs new file mode 100644 index 00000000..980989f2 --- /dev/null +++ b/crates/bytesbuf/examples/bb_reuse.rs @@ -0,0 +1,58 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +//! Showcases how you can easily and cheaply reuse `BytesView` in part or whole. + +use bytesbuf::{BytesView, GlobalPool}; + +fn main() { + // The global memory pool in real-world code would be provided by the application framework. + let memory = GlobalPool::new(); + + let hello_world = BytesView::copied_from_slice(b"Hello, world!", &memory); + + inspect_bytes(&hello_world); + + // Splitting up a view into sub-views is a cheap zero-copy operation. + let hello = hello_world.range(0..5); + let world = hello_world.range(7..12); + + inspect_bytes(&hello); + inspect_bytes(&world); + + // You can glue the parts back together if you wish. Again, this is a cheap zero-copy operation. + let hello_world_reconstructed = BytesView::from_views([hello, world]); + + inspect_bytes(&hello_world_reconstructed); + + // You can also append a BytesView into a BytesBuf. This is also a cheap zero-copy operation. + let mut buf = memory.reserve(1024); + + buf.put_slice(b"The quick brown fox says \"".as_slice()); + buf.put_bytes(hello_world_reconstructed); + buf.put_slice(b"\" and jumps over the lazy dog.".as_slice()); + + let fox_story = buf.consume_all(); + + inspect_bytes(&fox_story); +} + +fn inspect_bytes(bytes: &BytesView) { + let len = bytes.len(); + let mut slice_lengths = Vec::new(); + + // We need to mutate the view to slide our inspection window over it, so we clone it. + // Cloning a view is a cheap zero-copy operation; do not hesitate to do it when needed. + let mut bytes = bytes.clone(); + + while !bytes.is_empty() { + let slice = bytes.first_slice(); + slice_lengths.push(slice.len()); + + // We have completed processing this slice. All we wanted was to know its length. + // We can now mark this slice as consumed, revealing the next slice for inspection. + bytes.advance(slice.len()); + } + + println!("Inspected a view over {len} bytes with slice lengths: {slice_lengths:?}"); +} diff --git a/crates/bytesbuf/examples/mem_chunk_write.rs b/crates/bytesbuf/examples/bb_slice_by_slice_write.rs similarity index 55% rename from crates/bytesbuf/examples/mem_chunk_write.rs rename to crates/bytesbuf/examples/bb_slice_by_slice_write.rs index bb48d40b..465c08e5 100644 --- a/crates/bytesbuf/examples/mem_chunk_write.rs +++ b/crates/bytesbuf/examples/bb_slice_by_slice_write.rs @@ -1,73 +1,75 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -//! Showcases how to write to a sequence builder using mutable memory slices. +//! Showcases how to write to a `BytesBuf` by directly writing into slices of mutable memory. //! -//! Compared to the helper methods used in `mem_basic.rs`, writing via mutable slices is a more +//! Compared to the helper methods showcased in `bb_basic.rs`, writing via mutable slices is a more //! advanced technique that gives you greater control over the writing process at the expense of //! more complex code. //! -//! See also `mem_reuse.rs`, which includes an example of how to read chunk-by-chunk. +//! See also `bb_reuse.rs`, which includes a complementary example of how to read slice-by-slice. use std::ptr; -use bytes::BufMut; use bytesbuf::{BytesBuf, GlobalPool}; const LUCKY_NUMBER: usize = 8888; const FAVORITE_COLOR: &[u8] = b"octarine"; fn main() { - // The global memory pool in a real application would be provided by the framework. + // The global memory pool in real-world code would be provided by the application framework. let memory = GlobalPool::new(); // We emit the favorite color. For maximal happiness, we repeat it LUCKY_NUMBER times. let capacity_required = LUCKY_NUMBER * FAVORITE_COLOR.len(); - let mut sequence_builder = memory.reserve(capacity_required); + let mut buf = memory.reserve(capacity_required); for _ in 0..LUCKY_NUMBER { - emit_favorite_color(&mut sequence_builder); + emit_favorite_color(&mut buf); } - let sequence = sequence_builder.consume_all(); + let message = buf.consume_all(); println!( - "Emitted favorite color {LUCKY_NUMBER} times, resulting in a sequence of {} bytes.", - sequence.len() + "Emitted favorite color {LUCKY_NUMBER} times, resulting in a message of {} bytes.", + message.len() ); } -fn emit_favorite_color(sequence_builder: &mut BytesBuf) { - assert!(sequence_builder.has_remaining_mut(), "no remaining capacity in sequence builder"); +fn emit_favorite_color(buf: &mut BytesBuf) { + assert!( + buf.remaining_capacity() >= FAVORITE_COLOR.len(), + "insufficient remaining capacity in buffer" + ); let mut payload_to_write = FAVORITE_COLOR; while !payload_to_write.is_empty() { // This returns a mutable slice of uninitialized memory that we can fill. However, there is - // no guarantee on how many bytes this slice covers (it could be as little as 1 byte per chunk). - // We are required to fill all bytes before we can proceed to the next chunk (which may also + // no guarantee on how many bytes this slice covers (it could be as little as 1 byte per slice). + // We are required to fill all bytes before we can proceed to the next slice (which may also // be as small as 1 byte in length). - let chunk = sequence_builder.chunk_mut(); + let slice = buf.first_unfilled_slice(); - // It could be that we cannot write the entire payload to this chunk, so we write as much - // as we can and leave the remainder to the next chunk. - let bytes_to_write = chunk.len().min(payload_to_write.len()); + // It could be that we cannot write the entire payload to this slice, so we write as much + // as we can and leave the remainder to the next slice. + let bytes_to_write = slice.len().min(payload_to_write.len()); let slice_to_write = &payload_to_write[..bytes_to_write]; // Once write_copy_of_slice() is stabilized, we can replace this with a safe alternative. - // SAFETY: Both pointers are valid for reading/writing bytes, all is well. + // SAFETY: Both pointers are valid for reading/writing bytes and we guard length via min(). unsafe { - ptr::copy_nonoverlapping(slice_to_write.as_ptr(), chunk.as_mut_ptr(), bytes_to_write); + ptr::copy_nonoverlapping(slice_to_write.as_ptr(), slice.as_mut_ptr().cast(), bytes_to_write); } // SAFETY: We must have actually initialized this many bytes. We did. unsafe { - sequence_builder.advance_mut(bytes_to_write); + buf.advance(bytes_to_write); } // We wrote some (or all) of the payload, so keep whatever - // is left for the next loop iteration. + // is left for the next loop iteration if we did not write everything. payload_to_write = &payload_to_write[bytes_to_write..]; } } diff --git a/crates/bytesbuf/examples/mem_span_count_metric.rs b/crates/bytesbuf/examples/bb_slice_count_metric.rs similarity index 63% rename from crates/bytesbuf/examples/mem_span_count_metric.rs rename to crates/bytesbuf/examples/bb_slice_count_metric.rs index bcf2fd83..a0fcfd9f 100644 --- a/crates/bytesbuf/examples/mem_span_count_metric.rs +++ b/crates/bytesbuf/examples/bb_slice_count_metric.rs @@ -1,9 +1,9 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -//! Demonstrates how to manually extract the "how many spans in a sequence" metric from an app. +//! Demonstrates how to manually extract the "how many slices in a `BytesView`" metric from an app. //! -//! This is for internal use only, to help fine-tune the internal memory layout of byte sequences. +//! This is for internal use only, to help fine-tune the internal memory layout of `BytesView`. //! //! Reporting via metrics pipeline (e.g. OpenTelemetry) is also possible but out of scope here. @@ -14,14 +14,14 @@ fn main() { // In a real-world app, the memory provider would be supplied by the application framework. let memory = GlobalPool::new(); - // First a simple sequence with a single span. + // First a simple view consisting of a single slice. let sample1 = BytesView::copied_from_slice(b"Hello, world!", &memory); - // Which repeated 4 times gives us a sequence made up of 4 spans. - let sample4 = BytesView::from_sequences([sample1.clone(), sample1.clone(), sample1.clone(), sample1]); + // Which repeated 4 times gives us a sequence made up of 4 slices. + let sample4 = BytesView::from_views([sample1.clone(), sample1.clone(), sample1.clone(), sample1]); - // Which repeated 4 times gives us a sequence made up of 16 spans. - let _sample16 = BytesView::from_sequences([sample4.clone(), sample4.clone(), sample4.clone(), sample4]); + // Which repeated 4 times gives us a sequence made up of 16 slices. + let _sample16 = BytesView::from_views([sample4.clone(), sample4.clone(), sample4.clone(), sample4]); // Dump metrics to stdout. println!("{}", Report::collect()); diff --git a/crates/bytesbuf/examples/mem_static_data.rs b/crates/bytesbuf/examples/bb_static_data.rs similarity index 55% rename from crates/bytesbuf/examples/mem_static_data.rs rename to crates/bytesbuf/examples/bb_static_data.rs index 26784c15..76580da3 100644 --- a/crates/bytesbuf/examples/mem_static_data.rs +++ b/crates/bytesbuf/examples/bb_static_data.rs @@ -6,38 +6,52 @@ use std::io::Write; use std::sync::OnceLock; use std::time::{SystemTime, UNIX_EPOCH}; -use bytes::{Buf, BufMut}; use bytesbuf::{BytesView, HasMemory, Memory, MemoryShared, TransparentTestMemory}; -// We often want to write this static data to network connections. +// We often want to write static fragments as part of network communications. const HEADER_PREFIX: &[u8] = b"Unix-Milliseconds: "; +const TWO_NEWLINES: &[u8] = b"\r\n\r\n"; fn main() { // We transform the static data into a BytesView on first use, via OnceLock. + // + // You are expected to reuse this variable as long as the context does not change. + // For example, it is typically fine to share this across multiple network connections + // because they all likely use the same memory configuration. However, writing to files + // may require a different memory configuration for optimality, so you would need a different + // `BytesView` for that. Such details will typically be documented in the API documentation + // of the type that consumes the `BytesView` (e.g. a network connection or a file writer). let header_prefix = OnceLock::::new(); + let two_newlines = OnceLock::::new(); // Accept some connections and send a response to each of them. for _ in 0..10 { let mut connection = Connection::accept(); - // The static data is transformed into a BytesView on first use, - // using memory optimally configured for a network connection. + // The static data is transformed into a BytesView on first use, using memory optimally configured + // for network connections. The underlying principle is that memory optimally configured for one network + // connection is likely also optimally configured for another network connection, enabling efficient reuse. let header_prefix = header_prefix.get_or_init(|| BytesView::copied_from_slice(HEADER_PREFIX, &connection.memory())); + let two_newlines = two_newlines.get_or_init(|| BytesView::copied_from_slice(TWO_NEWLINES, &connection.memory())); - // Note that reused BytesViews do not consume any memory capacity from the builder, - // so we only need to account for the timestamp bytes and the trailing CRLFs. - let mut response_builder = connection.memory().reserve(TIMESTAMP_MAX_LEN + 4); + // Note that reused BytesViews do not consume any memory capacity, so when making a reservation + // for the response message, we only need to account for the timestamp bytes. + let mut response_buf = connection.memory().reserve(TIMESTAMP_MAX_LEN); - // Reuse the byte sequence. Cloning a BytesView is a cheap zero-copy operation. - response_builder.append(header_prefix.clone()); + // Insert the static prefix. Cloning a BytesView is a cheap zero-copy operation. + response_buf.put_bytes(header_prefix.clone()); + // We cannot assume that a `BytesBuf` contains consecutive memory, + // so any fixed-length processing must be done using temporary buffers. let mut stringification_buffer = [0u8; TIMESTAMP_MAX_LEN]; let timestamp_bytes = serialize_timestamp(&mut stringification_buffer); - response_builder.put(timestamp_bytes); - response_builder.put(b"\r\n\r\n".as_slice()); + response_buf.put_slice(timestamp_bytes); - connection.write(response_builder.consume_all()); + // Insert the static suffix. Cloning a BytesView is a cheap zero-copy operation. + response_buf.put_bytes(two_newlines.clone()); + + connection.write(response_buf.consume_all()); } } @@ -77,10 +91,10 @@ impl Connection { fn write(&mut self, mut message: BytesView) { print!("Sent message: "); - while message.has_remaining() { - let chunk = message.chunk(); - print!("{}", String::from_utf8_lossy(chunk)); - message.advance(chunk.len()); + while !message.is_empty() { + let slice = message.first_slice(); + print!("{}", String::from_utf8_lossy(slice)); + message.advance(slice.len()); } println!(); diff --git a/crates/bytesbuf/examples/mem_reuse.rs b/crates/bytesbuf/examples/mem_reuse.rs deleted file mode 100644 index 941b5c5f..00000000 --- a/crates/bytesbuf/examples/mem_reuse.rs +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -//! Showcases how you can easily and cheaply reuse byte sequences and parts of byte sequences. -use bytes::{Buf, BufMut}; -use bytesbuf::{BytesView, GlobalPool}; - -fn main() { - // The global memory pool in a real application would be provided by the framework. - let memory = GlobalPool::new(); - - let hello_world = BytesView::copied_from_slice(b"Hello, world!", &memory); - - inspect_sequence(&hello_world); - - // Splitting up a sequence into sub-sequences is a cheap zero-copy operation. - let hello = hello_world.slice(0..5); - let world = hello_world.slice(7..12); - - inspect_sequence(&hello); - inspect_sequence(&world); - - // You can glue the parts back together if you wish. Again, this is a cheap zero-copy operation. - let hello_world_reconstructed = BytesView::from_sequences([hello, world]); - - inspect_sequence(&hello_world_reconstructed); - - // You can also shove existing sequences into a sequence builder that is in the process - // of creating a new byte sequence. This is also a cheap zero-copy operation. - let mut sequence_builder = memory.reserve(1024); - - sequence_builder.put(b"The quick brown fox says \"".as_slice()); - sequence_builder.append(hello_world_reconstructed); - sequence_builder.put(b"\" and jumps over the lazy dog.".as_slice()); - - let fox_story = sequence_builder.consume_all(); - - inspect_sequence(&fox_story); -} - -fn inspect_sequence(sequence: &BytesView) { - let len = sequence.len(); - let mut chunk_lengths = Vec::new(); - - // We need to mutate the sequence to slide our inspection window over it, so we clone it. - // Cloning a sequence is a cheap zero-copy operation, do not hesitate to do it when needed. - let mut sequence = sequence.clone(); - - while sequence.has_remaining() { - let chunk = sequence.chunk(); - chunk_lengths.push(chunk.len()); - - // We have completed processing this chunk, all we wanted was to know its length. - sequence.advance(chunk.len()); - } - - println!("Inspected a sequence of {len} bytes with chunk lengths: {chunk_lengths:?}"); -} diff --git a/crates/bytesbuf/src/block_ref.rs b/crates/bytesbuf/src/block_ref.rs index 0c477def..b7545bf1 100644 --- a/crates/bytesbuf/src/block_ref.rs +++ b/crates/bytesbuf/src/block_ref.rs @@ -5,7 +5,7 @@ use std::any::Any; use std::marker::PhantomData; use std::ptr::NonNull; -/// A reference to a block of memory capacity owned by a memory provider and leased to user code. +/// References a block of memory capacity rented from a memory provider. /// /// While a memory provider only leases each block to one caller at a time, this caller may further /// share and subdivide the block between multiple co-owners. These co-owners will coordinate the diff --git a/crates/bytesbuf/src/buf.rs b/crates/bytesbuf/src/buf.rs index 65f7737e..64d0e5bf 100644 --- a/crates/bytesbuf/src/buf.rs +++ b/crates/bytesbuf/src/buf.rs @@ -1,46 +1,56 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +use std::any::type_name; use std::mem::{self, MaybeUninit}; use std::num::NonZero; -use bytes::buf::UninitSlice; -use bytes::{Buf, BufMut}; use smallvec::SmallVec; use crate::{Block, BlockSize, BytesBufWrite, BytesView, MAX_INLINE_SPANS, Memory, MemoryGuard, Span, SpanBuilder}; -/// Owns some memory capacity in which it allows you to place a sequence of bytes that -/// you can thereafter extract as one or more [`BytesView`]s. +/// Assembles byte sequences in reserved memory, exposing them as [`BytesView`]s. /// -/// The capacity of the `BytesBuf` must be reserved in advance via [`reserve()`][3] before -/// you can fill it with data. +/// The buffer owns some memory capacity into which it allows you to write a sequence of bytes that +/// you can thereafter extract as one or more [`BytesView`]s over immutable data. Mutation of the +/// buffer contents is append-only - once data has been written into the buffer, it cannot be modified. +/// +/// Capacity must be reserved in advance (e.g. via [`reserve()`]) before you can write data into the buffer. +/// The exception to this is when appending an existing [`BytesView`] via [`put_bytes()`] because +/// appending a [`BytesView`] is a zero-copy operation that reuses the view's existing memory capacity. /// /// # Memory capacity /// -/// A single `BytesBuf` can use memory capacity from any [memory provider][7], including a -/// mix of different memory providers for the same `BytesBuf` instance. All methods that -/// extend the memory capacity require the caller to provide a reference to the memory provider. +/// A single `BytesBuf` can use memory capacity from any [memory provider], including a +/// mix of different memory providers. All methods that extend the memory capacity require the caller +/// to provide a reference to the memory provider to use. +/// +/// When data is extracted from the buffer by consuming it (via [`consume()`] or [`consume_all()`]), +/// ownership of the used memory capacity is transferred to the returned [`BytesView`]. Any leftover +/// memory capacity remains in the buffer, ready to receive further writes. /// /// # Conceptual design /// -/// The memory owned by a `BytesBuf` (its capacity) can be viewed as two regions: +/// The memory capacity owned by a `BytesBuf` can be viewed as two regions: /// -/// * Filled memory - these bytes have been written to but have not yet been consumed as a -/// [`BytesView`]. They may be peeked at (via [`peek()`][4]) or consumed (via [`consume()`][5]). -/// * Available memory - these bytes have not yet been written to and are available for writing via -/// [`bytes::buf::BufMut`][1] or [`begin_vectored_write()`][Self::begin_vectored_write]. +/// * Filled memory - data has been written into this memory but this data has not yet been consumed as a +/// [`BytesView`]. Nevertheless, this data may already be in use because it may have been exposed via +/// [`peek()`], which does not consume it from the buffer. Memory capacity is removed from this region +/// when bytes are consumed from the buffer. +/// * Available memory - no data has been written into this memory. Calling any of the write methods on +/// `BytesBuf` will write data to the start of this region and transfer the affected capacity to the +/// filled memory region. /// -/// Existing [`BytesView`]s can be appended to the [`BytesBuf`] via [`append()`][6] without -/// consuming capacity (each appended [`BytesView`] brings its own backing memory capacity). +/// Existing [`BytesView`]s can be appended to the `BytesBuf` via [`put_bytes()`] without +/// consuming capacity as each appended [`BytesView`] brings its own backing memory capacity. #[doc = include_str!("../doc/snippets/sequence_memory_layout.md")] /// -/// [1]: https://docs.rs/bytes/latest/bytes/buf/trait.BufMut.html -/// [3]: Self::reserve -/// [4]: Self::peek -/// [5]: Self::consume -/// [6]: Self::append -/// [7]: crate::Memory +/// [memory provider]: crate::Memory +/// [`reserve()`]: Self::reserve +/// [`put_bytes()`]: Self::put_bytes +/// [`consume()`]: Self::consume +/// [`consume_all()`]: Self::consume_all +/// [`peek()`]: Self::peek #[derive(Default)] pub struct BytesBuf { // The frozen spans are at the front of the sequence being built and have already become @@ -92,21 +102,26 @@ pub struct BytesBuf { } impl BytesBuf { - /// Creates an instance with 0 bytes of capacity. + /// Creates an instance without any memory capacity. #[must_use] pub fn new() -> Self { Self::default() } - /// Creates an instance that takes exclusive ownership of the capacity in the - /// provided memory blocks. + /// Creates an instance that owns the provided memory blocks. /// - /// This is used by implementations of memory providers. To obtain a `BytesBuf` with - /// available memory capacity, you need to use an implementation of [`Memory`] that - /// provides you instances of `BytesBuf`. + /// This is the API used by memory providers to issue rented memory capacity to callers. + /// Unless you are implementing a memory provider, you will not need to call this function. + /// Instead, use either [`Memory::reserve()`] or [`BytesBuf::reserve()`]. + /// + /// # Blocks are unordered /// /// There is no guarantee that the `BytesBuf` uses the blocks in the order provided to /// this function. Blocks may be used in any order. + /// + /// [`Memory::reserve()`]: Memory::reserve + /// [`BytesBuf::reserve()`]: Self::reserve + #[must_use] pub fn from_blocks(blocks: I) -> Self where I: IntoIterator, @@ -120,7 +135,7 @@ impl BytesBuf { { let span_builders: SmallVec<[SpanBuilder; MAX_INLINE_SPANS]> = span_builders.into_iter().collect(); - let available = span_builders.iter().map(SpanBuilder::remaining_mut).sum(); + let available = span_builders.iter().map(SpanBuilder::remaining_capacity).sum(); Self { frozen_spans: SmallVec::new_const(), @@ -133,48 +148,60 @@ impl BytesBuf { } } - /// Adds memory capacity to the sequence builder, ensuring there is enough capacity to - /// accommodate `additional_bytes` of content in addition to existing content already present. + /// Adds enough memory capacity to accommodate at least `additional_bytes` of content. + /// + /// After this call, [`remaining_capacity()`] will be at least `additional_bytes`. + /// + /// The memory provider may provide more capacity than requested - `additional_bytes` is only a lower bound. /// - /// The requested reserve capacity may be extended further if the memory provider considers it - /// more efficient to use a larger block of memory than strictly required for this operation. + /// # Panics + /// + /// Panics if the resulting total buffer capacity would be greater than `usize::MAX`. + /// + /// [`remaining_capacity()`]: Self::remaining_capacity pub fn reserve(&mut self, additional_bytes: usize, memory_provider: &impl Memory) { - let bytes_needed = additional_bytes.saturating_sub(self.remaining_mut()); + let bytes_needed = additional_bytes.saturating_sub(self.remaining_capacity()); - if bytes_needed == 0 { + let Some(bytes_needed) = NonZero::new(bytes_needed) else { return; - } + }; self.extend_capacity_by_at_least(bytes_needed, memory_provider); } - fn extend_capacity_by_at_least(&mut self, bytes: usize, memory_provider: &impl Memory) { - let additional_memory = memory_provider.reserve(bytes); + fn extend_capacity_by_at_least(&mut self, bytes: NonZero, memory_provider: &impl Memory) { + let additional_memory = memory_provider.reserve(bytes.get()); - // For extra paranoia. We expect a memory provider to return an empty sequence builder. - debug_assert!(additional_memory.capacity() >= bytes); + // For extra paranoia. We expect a memory provider to return an empty buffer. + debug_assert!(additional_memory.capacity() >= bytes.get()); debug_assert!(additional_memory.is_empty()); self.available = self .available .checked_add(additional_memory.capacity()) - .expect("usize overflow should be impossible here because the sequence builder capacity would exceed virtual memory size"); + .expect("buffer capacity cannot exceed usize::MAX"); // We put the new ones in front (existing content needs to stay at the end). self.span_builders_reversed.insert_many(0, additional_memory.span_builders_reversed); } - /// Appends the given sequence to the end of the sequence builder's filled bytes region. + /// Appends the contents of an existing [`BytesView`] to the end of the buffer. /// - /// This automatically extends the builder's capacity with the memory capacity used of the - /// appended sequence, for a net zero change in remaining available capacity. - #[expect(clippy::missing_panics_doc, reason = "only unreachable panics")] - pub fn append(&mut self, sequence: BytesView) { - if !sequence.has_remaining() { + /// Memory capacity of the existing [`BytesView`] is reused without copying. + /// + /// This is a private API to keep the nitty-gritty of span bookkeeping contained in this file + /// while the public API lives in another file for ease of maintenance. The equivalent + /// public API is `put_bytes()`. + /// + /// # Panics + /// + /// Panics if the resulting total buffer capacity would be greater than `usize::MAX`. + pub(crate) fn append(&mut self, bytes: BytesView) { + if bytes.is_empty() { return; } - let sequence_len = sequence.len(); + let bytes_len = bytes.len(); // Only the first span builder may hold unfrozen data (the rest are for spare capacity). let total_unfrozen_bytes = NonZero::new(self.span_builders_reversed.last().map_or(0, SpanBuilder::len)); @@ -188,33 +215,38 @@ impl BytesBuf { debug_assert!(self.span_builders_reversed.last().map_or(0, SpanBuilder::len) == 0); } - self.frozen_spans.extend(sequence.into_spans_reversed().into_iter().rev()); - - self.len = self - .len - .checked_add(sequence_len) - .expect("usize overflow should be impossible here because the sequence builder capacity would exceed virtual memory size"); + // We do this first so if we do panic, we have not performed any incomplete operations. + // The freezing above is safe even if we panic here - freezing is an atomic operation. + self.len = self.len.checked_add(bytes_len).expect("buffer capacity cannot exceed usize::MAX"); // Any appended BytesView is frozen by definition, as contents of a BytesView are immutable. - self.frozen = self - .frozen - .checked_add(sequence_len) - .expect("usize overflow should be impossible here because the sequence builder capacity would exceed virtual memory size"); + // This cannot wrap because we verified `len` is in-bounds and `frozen <= len` is a type invariant. + self.frozen = self.frozen.wrapping_add(bytes_len); + + self.frozen_spans.extend(bytes.into_spans_reversed().into_iter().rev()); } - /// Peeks at the contents of the filled bytes region, returning a [`BytesView`] over all - /// filled data without consuming it from the sequence builder. + /// Peeks at the contents of the filled bytes region. /// - /// This is similar to [`consume_all()`][Self::consume_all] except the data remains in the - /// sequence builder and can still be consumed later. + /// The returned [`BytesView`] covers all data in the buffer but does not consume any of the data. + /// + /// Functionally similar to [`consume_all()`] except all the data remains in the + /// buffer and can still be consumed later. + /// + /// [`consume_all()`]: Self::consume_all #[must_use] pub fn peek(&self) -> BytesView { // Build a list of all spans to include in the result, in reverse order for efficient construction. let mut result_spans_reversed: SmallVec<[Span; MAX_INLINE_SPANS]> = SmallVec::new(); - // Add any filled data from the first span builder. + // Add any filled data from the first (potentially partially filled) span builder. if let Some(first_builder) = self.span_builders_reversed.last() { + // We only peek the span builder, as well. This is to avoid freezing it because freezing + // has security/performance implications and the motivating idea behind peeking is to + // verify the contents are ready for processing before we commit to freezing them. let span = first_builder.peek(); + + // It might just be empty - that's also fine. if !span.is_empty() { result_spans_reversed.push(span); } @@ -227,7 +259,7 @@ impl BytesBuf { BytesView::from_spans_reversed(result_spans_reversed) } - /// Length of the filled bytes region, ready to be consumed. + /// How many bytes of data are in the buffer, ready to be consumed. #[must_use] #[cfg_attr(debug_assertions, expect(clippy::missing_panics_doc, reason = "only unreachable panics"))] pub fn len(&self) -> usize { @@ -242,58 +274,60 @@ impl BytesBuf { let frozen_len = self.frozen_spans.iter().map(|x| x.len() as usize).sum::(); let unfrozen_len = self.span_builders_reversed.last().map_or(0, SpanBuilder::len) as usize; - frozen_len - .checked_add(unfrozen_len) - .expect("usize overflow should be impossible here because the sequence builder would exceed virtual memory size") + // Will not overflow - `capacity <= usize::MAX` is a type invariant and obviously `len < capacity`. + frozen_len.wrapping_add(unfrozen_len) } - /// Whether the filled bytes region is empty, i.e. contains no bytes that can be consumed. + /// Whether the buffer is empty (contains no data). /// - /// This does not imply that the sequence builder has no remaining capacity. + /// This does not imply that the buffer has no remaining memory capacity. #[must_use] pub fn is_empty(&self) -> bool { self.len() == 0 } - /// The total capacity of the sequence builder. + /// The total capacity of the buffer. /// - /// This is the sum of the length of the filled bytes and the available bytes regions. + /// This is the total length of the filled bytes and the available bytes regions. #[must_use] - #[expect(clippy::missing_panics_doc, reason = "only unreachable panics")] pub fn capacity(&self) -> usize { - self.len() - .checked_add(self.remaining_mut()) - .expect("usize overflow should be impossible here because the sequence builder would exceed virtual memory size") + // Will not overflow - `capacity <= usize::MAX` is a type invariant. + self.len().wrapping_add(self.remaining_capacity()) } - /// How many more bytes can be written into the sequence builder before its memory capacity - /// is exhausted. - #[cfg_attr(test, mutants::skip)] // Lying about length is an easy way to infinite loops. - pub fn remaining_mut(&self) -> usize { + /// How many more bytes can be written into the buffer + /// before its memory capacity is exhausted. + #[cfg_attr(test, mutants::skip)] // Lying about buffer sizes is an easy way to infinite loops. + pub fn remaining_capacity(&self) -> usize { // The remaining capacity is the sum of the remaining capacity of all span builders. debug_assert_eq!( self.available, - self.span_builders_reversed.iter().map(bytes::BufMut::remaining_mut).sum::() + self.span_builders_reversed + .iter() + .map(SpanBuilder::remaining_capacity) + .sum::() ); self.available } - /// Consumes `len` bytes from the beginning of the filled bytes region, - /// returning a [`BytesView`] with those bytes. + /// Consumes `len` bytes from the beginning of the buffer. + /// + /// The consumed bytes and the memory capacity that backs them are removed from the buffer. /// /// # Panics /// - /// Panics if the filled bytes region does not contain at least `len` bytes. + /// Panics if the buffer does not contain at least `len` bytes. pub fn consume(&mut self, len: usize) -> BytesView { self.consume_checked(len) - .expect("attempted to consume more bytes than available in builder") + .expect("attempted to consume more bytes than available in buffer") } - /// Consumes `len` bytes from the beginning of the filled bytes region, - /// returning a [`BytesView`] with those bytes. + /// Consumes `len` bytes from the beginning of the buffer. /// - /// Returns `None` if the filled bytes region does not contain at least `len` bytes. + /// Returns `None` if the buffer does not contain at least `len` bytes. + /// + /// The consumed bytes and the memory capacity that backs them are removed from the buffer. #[expect(clippy::missing_panics_doc, reason = "only unreachable panics")] pub fn consume_checked(&mut self, len: usize) -> Option { if len > self.len() { @@ -321,16 +355,21 @@ impl BytesBuf { let take = partially_consumed_frozen_span.slice(0..manifest.consume_partial_span_bytes); result_spans_reversed.push(take); - partially_consumed_frozen_span.advance(manifest.consume_partial_span_bytes as usize); + // SAFETY: We must guarantee that we do not try to advance out of bounds. This is guaranteed + // by the manifest calculation, the job of which is to determine the right in-bounds value. + unsafe { partially_consumed_frozen_span.advance(manifest.consume_partial_span_bytes as usize) }; } // We extend the result spans with the (storage-order) fully detached spans. // BytesBuf stores the frozen spans in content order, so we must reverse. result_spans_reversed.extend(self.frozen_spans.drain(..manifest.detach_complete_frozen_spans).rev()); - self.len = self.len.checked_sub(len).expect("guarded by if-block above"); + // Will not wrap because we verified bounds above. + self.len = self.len.wrapping_sub(len); - self.frozen = self.frozen.checked_sub(len).expect("any data consumed must have first been frozen"); + // Will not wrap because all consumed data must first have been frozen, + // which we guarantee via ensure_frozen() above. + self.frozen = self.frozen.wrapping_sub(len); Some(BytesView::from_spans_reversed(result_spans_reversed)) } @@ -344,9 +383,9 @@ impl BytesBuf { let span_len = span.len(); if span_len as usize <= len { - detach_complete_frozen_spans = detach_complete_frozen_spans - .checked_add(1) - .expect("span count can never exceed virtual memory size"); + // Will not wrap because a type invariant is `capacity <= usize::MAX`, so if + // capacity is in-bounds, the number of spans could not possibly be greater. + detach_complete_frozen_spans = detach_complete_frozen_spans.wrapping_add(1); len = len .checked_sub(span_len as usize) @@ -365,13 +404,16 @@ impl BytesBuf { ConsumeManifest { detach_complete_frozen_spans, // If any `len` was left, it was not a full span. - consume_partial_span_bytes: len.try_into().expect("we are supposed to have less than one span of data remaining but its length does not fit into a single memory block - algorithm defect"), + consume_partial_span_bytes: len.try_into().expect("we are supposed to have less than one memory block worth of data remaining but its length does not fit into a single memory block - algorithm defect"), } } - /// Consumes all filled bytes (if any), returning a [`BytesView`] with those bytes. + /// Consumes all bytes in the buffer. + /// + /// The consumed bytes and the memory capacity that backs them are removed from the buffer. pub fn consume_all(&mut self) -> BytesView { - self.consume_checked(self.len()).unwrap_or_default() + // SAFETY: Consuming len() bytes from self cannot possibly be out of bounds. + unsafe { self.consume_checked(self.len()).unwrap_unchecked() } } /// Consumes `len` bytes from the first span builder and moves it to the frozen spans list. @@ -386,7 +428,7 @@ impl BytesBuf { let span = span_builder.consume(len); self.frozen_spans.push(span); - if span_builder.remaining_mut() == 0 { + if span_builder.remaining_capacity() == 0 { // No more capacity left in this builder, so drop it. self.span_builders_reversed.pop(); } @@ -418,49 +460,52 @@ impl BytesBuf { self.freeze_from_first(must_freeze_bytes); } - /// The first consecutive slice of memory that makes up the remaining - /// capacity of the sequence builder. + /// The first slice of memory in the remaining capacity of the buffer. /// - /// After writing data to the start of this chunk, call `advance_mut()` to indicate - /// how many bytes have been filled with data. The next call to `chunk_mut()` will - /// return the next consecutive slice of memory you can fill. - pub fn chunk_mut(&mut self) -> &mut bytes::buf::UninitSlice { - // We are required to always return something, even if we have no span builders! - self.span_builders_reversed - .last_mut() - .map_or_else(|| UninitSlice::uninit(&mut []), |x| x.chunk_mut()) - } - - /// Advances the write head by `count` bytes, indicating that this many bytes from the start - /// of [`chunk_mut()`][1] have been filled with data. + /// This allows you to manually write into the buffer instead of using the various + /// provided convenience methods. Only the first slice of the remaining capacity is + /// exposed at any given time by this API. /// - /// After this call, the indicated number of additional bytes may be consumed from the builder. + /// After writing data to the start of this slice, call [`advance()`] to indicate + /// how many bytes have been filled with data. The next call to `first_unfilled_slice()` + /// will return the next slice of memory you can write into. This slice must be + /// completely filled before the next slice is exposed (a partial fill will simply + /// return the remaining range from the same slice in the next call). /// - /// # Panics + /// To write to multiple slices concurrently, use [`begin_vectored_write()`]. + #[doc = include_str!("../doc/snippets/sequence_memory_layout.md")] /// - /// Panics if `count` is greater than the length of [`chunk_mut()`][1]. + /// [`advance()`]: Self::advance + /// [`begin_vectored_write()`]: Self::begin_vectored_write + pub fn first_unfilled_slice(&mut self) -> &mut [MaybeUninit] { + if let Some(last) = self.span_builders_reversed.last_mut() { + last.unfilled_slice_mut() + } else { + // We are required to always return something, even if we have no span builders! + &mut [] + } + } + + /// Signals that `count` bytes have been written to the start of [`first_unfilled_slice()`]. + /// + /// The next call to [`first_unfilled_slice()`] will return the next slice of memory that + /// can be filled with data. /// /// # Safety /// - /// The caller must guarantee that the indicated number of bytes have been initialized with - /// data, sequentially starting from the beginning of the chunk returned by [`chunk_mut()`][1]. + /// The caller must guarantee that `count` bytes from the beginning of [`first_unfilled_slice()`] + /// have been initialized. /// - /// [1]: Self::chunk_mut - pub unsafe fn advance_mut(&mut self, count: usize) { + /// [`first_unfilled_slice()`]: Self::first_unfilled_slice + #[expect(clippy::missing_panics_doc, reason = "only unreachable panics")] + pub unsafe fn advance(&mut self, count: usize) { if count == 0 { return; } - // Advancing the writer by more than a single chunk's length is an error, at least under - // the current implementation that does not support vectored BufMut access. - assert!( - count - <= self - .span_builders_reversed - .last() - .expect("attempted to BufMut::advance_mut() when out of memory capacity - API contract violation") - .remaining_mut() - ); + // The write head can only be advanced (via this method) up to the end of the first slice, no further. + // This is guaranteed by our safety requirements, so we only assert this in debug builds for extra validation. + debug_assert!(count <= self.span_builders_reversed.last().map_or(0, SpanBuilder::remaining_capacity)); let span_builder = self .span_builders_reversed @@ -468,9 +513,9 @@ impl BytesBuf { .expect("there must be at least one span builder if we wrote nonzero bytes"); // SAFETY: We simply rely on the caller's safety promises here, "forwarding" them. - unsafe { span_builder.advance_mut(count) }; + unsafe { span_builder.advance(count) }; - if span_builder.remaining_mut() == 0 { + if span_builder.remaining_capacity() == 0 { // The span builder is full, so we need to freeze it and move it to the frozen spans. let len = NonZero::new(span_builder.len()) .expect("there is no capacity left in the span builder so there must be at least one byte to consume unless we somehow left an empty span builder in the queue"); @@ -478,7 +523,12 @@ impl BytesBuf { self.freeze_from_first(len); // Debug build paranoia: no full span remains after freeze, right? - debug_assert!(self.span_builders_reversed.last().map_or(usize::MAX, BufMut::remaining_mut) > 0); + debug_assert!( + self.span_builders_reversed + .last() + .map_or(usize::MAX, SpanBuilder::remaining_capacity) + > 0 + ); } self.len = self @@ -492,59 +542,62 @@ impl BytesBuf { .expect("guarded by assertion above - we must have at least this much capacity still available"); } - /// Begins a vectored write operation that takes exclusive ownership of the sequence builder - /// for the duration of the operation and allows individual slices of available capacity to be - /// filled concurrently, up to an optional limit of `max_len` bytes. + /// Concurrently writes data into all the byte slices that make up the buffer. + /// + /// The vectored write takes exclusive ownership of the buffer for the duration of the operation + /// and allows individual slices of the remaining capacity to be filled concurrently, up to an + /// optional limit of `max_len` bytes. /// /// Some I/O operations are naturally limited to a maximum number of bytes that can be - /// transferred, so the length limit here allows us to project a restricted view of the - /// available capacity to the caller without having to limit the true capacity of the builder. + /// transferred, so the length limit here allows you to project a restricted view of the + /// available capacity without having to limit the true capacity of the buffer. /// /// # Panics /// - /// Panics if `max_len` is greater than the remaining capacity of the sequence builder. + /// Panics if `max_len` is greater than the remaining capacity of the buffer. pub fn begin_vectored_write(&mut self, max_len: Option) -> BytesBufVectoredWrite<'_> { self.begin_vectored_write_checked(max_len) .expect("attempted to begin a vectored write with a max_len that was greater than the remaining capacity") } - /// Begins a vectored write operation that takes exclusive ownership of the sequence builder - /// for the duration of the operation and allows individual slices of available capacity to be - /// filled concurrently, up to an optional limit of `max_len` bytes. + /// Concurrently writes data into all the byte slices that make up the buffer. + /// + /// The vectored write takes exclusive ownership of the buffer for the duration of the operation + /// and allows individual slices of the remaining capacity to be filled concurrently, up to an + /// optional limit of `max_len` bytes. /// /// Some I/O operations are naturally limited to a maximum number of bytes that can be - /// transferred, so the length limit here allows us to project a restricted view of the - /// available capacity to the caller without having to limit the true capacity of the builder. + /// transferred, so the length limit here allows you to project a restricted view of the + /// available capacity without having to limit the true capacity of the buffer. /// /// # Returns /// - /// Returns `None` if `max_len` is greater than the remaining capacity of the sequence builder. + /// Returns `None` if `max_len` is greater than the remaining capacity of the buffer. pub fn begin_vectored_write_checked(&mut self, max_len: Option) -> Option> { if let Some(max_len) = max_len - && max_len > self.remaining_mut() + && max_len > self.remaining_capacity() { return None; } - Some(BytesBufVectoredWrite { builder: self, max_len }) + Some(BytesBufVectoredWrite { buf: self, max_len }) } fn iter_available_capacity(&mut self, max_len: Option) -> BytesBufAvailableIterator<'_> { let next_span_builder_index = if self.span_builders_reversed.is_empty() { None } else { Some(0) }; BytesBufAvailableIterator { - builder: self, + buf: self, next_span_builder_index, max_len, } } - /// Creates a memory guard that extends the lifetime of the memory blocks that provide the - /// backing memory capacity for this sequence builder. + /// Extends the lifetime of the memory capacity backing this buffer. /// - /// This can be useful when unsafe code is used to reference the contents of a `BytesBuf` - /// and it is possible to reach a condition where the `BytesBuf` itself no longer exists, - /// even though the contents are referenced (e.g. because this is happening in non-Rust code). + /// This can be useful when unsafe code is used to reference the contents of a `BytesBuf` and it + /// is possible to reach a condition where the `BytesBuf` itself no longer exists, even though + /// the contents are referenced (e.g. because the remaining references are in non-Rust code). pub fn extend_lifetime(&self) -> MemoryGuard { MemoryGuard::new( self.span_builders_reversed @@ -565,33 +618,9 @@ impl BytesBuf { } } -// SAFETY: The trait documentation does not define any safety requirements we need to fulfill. -// It is unclear why the trait is marked unsafe in the first place. -unsafe impl BufMut for BytesBuf { - #[cfg_attr(test, mutants::skip)] // Trivial forwarder. - #[inline] - fn remaining_mut(&self) -> usize { - self.remaining_mut() - } - - #[cfg_attr(test, mutants::skip)] // Trivial forwarder. - #[inline] - unsafe fn advance_mut(&mut self, cnt: usize) { - // SAFETY: Forwarding safety requirements to the caller. - unsafe { - self.advance_mut(cnt); - } - } - - #[cfg_attr(test, mutants::skip)] // Trivial forwarder. - #[inline] - fn chunk_mut(&mut self) -> &mut bytes::buf::UninitSlice { - self.chunk_mut() - } -} - impl std::fmt::Debug for BytesBuf { #[cfg_attr(test, mutants::skip)] // We have no API contract here. + #[cfg_attr(coverage_nightly, coverage(off))] // We have no API contract here. fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { let frozen_spans = self.frozen_spans.iter().map(|x| x.len().to_string()).collect::>().join(", "); @@ -601,15 +630,15 @@ impl std::fmt::Debug for BytesBuf { .rev() .map(|x| { if x.is_empty() { - x.remaining_mut().to_string() + x.remaining_capacity().to_string() } else { - format!("{} + {}", x.len(), x.remaining_mut()) + format!("{} + {}", x.len(), x.remaining_capacity()) } }) .collect::>() .join(", "); - f.debug_struct("BytesBuf") + f.debug_struct(type_name::()) .field("len", &self.len) .field("frozen", &self.frozen) .field("available", &self.available) @@ -633,73 +662,74 @@ struct ConsumeManifest { impl ConsumeManifest { const fn required_spans_capacity(&self) -> usize { if self.consume_partial_span_bytes != 0 { - self.detach_complete_frozen_spans - .checked_add(1) - .expect("span count cannot exceed virtual memory size") + // This will not wrap because a type invariant is `capacity <= usize::MAX`, so if + // capacity is already in-bounds, the count of spans certainly is not a greater number. + self.detach_complete_frozen_spans.wrapping_add(1) } else { self.detach_complete_frozen_spans } } } -/// A vectored write is an operation that concurrently writes data into multiple chunks -/// of memory owned by a `BytesBuf`. +/// Concurrently writes data into multiple slices of buffer memory capacity. /// /// The operation takes exclusive ownership of the `BytesBuf`. During the vectored write, /// the remaining capacity of the `BytesBuf` is exposed as `MaybeUninit` slices /// that at the end of the operation must be filled sequentially and in order, without gaps, /// in any desired amount (from 0 bytes written to all slices filled). /// -/// The capacity used during the operation can optionally be limited to `max_len` bytes. +/// The capacity exposed during the operation can optionally be limited to `max_len` bytes. /// -/// The operation is completed by calling `.commit()` on the instance, after which the instance is +/// The operation is completed by calling `.commit()` on the instance, after which the operation is /// consumed and the exclusive ownership of the `BytesBuf` released. /// -/// If the type is dropped without committing, the operation is aborted and all remaining capacity +/// If the instance is dropped without committing, the operation is aborted and all remaining capacity /// is left in a potentially uninitialized state. #[derive(Debug)] pub struct BytesBufVectoredWrite<'a> { - builder: &'a mut BytesBuf, + buf: &'a mut BytesBuf, max_len: Option, } impl BytesBufVectoredWrite<'_> { - /// Iterates over the chunks of available capacity in the sequence builder, + /// Iterates over the slices of available capacity of the buffer, /// allowing them to be filled with data. - pub fn iter_chunks_mut(&mut self) -> BytesBufAvailableIterator<'_> { - self.builder.iter_available_capacity(self.max_len) + /// + /// The slices returned from this iterator have the lifetime of the vectored + /// write operation itself, allowing them to be mutated concurrently. + pub fn iter_slices_mut(&mut self) -> BytesBufAvailableIterator<'_> { + self.buf.iter_available_capacity(self.max_len) } - /// Creates a memory guard that extends the lifetime of the memory blocks that provide the - /// backing memory capacity for this sequence builder. + /// Extends the lifetime of the memory capacity backing this buffer. /// - /// This can be useful when unsafe code is used to reference the contents of a `BytesBuf` - /// and it is possible to reach a condition where the `BytesBuf` itself no longer exists, - /// even though the contents are referenced (e.g. because this is happening in non-Rust code). + /// This can be useful when unsafe code is used to reference the contents of a `BytesBuf` and it + /// is possible to reach a condition where the `BytesBuf` itself no longer exists, even though + /// the contents are referenced (e.g. because the remaining references are in non-Rust code). pub fn extend_lifetime(&self) -> MemoryGuard { - self.builder.extend_lifetime() + self.buf.extend_lifetime() } /// Completes the vectored write operation, committing `bytes_written` bytes of data that - /// sequentially and completely fills chunks from the start of the provided chunks. + /// sequentially and completely fills slices from the start of the provided slices. /// /// # Safety /// /// The caller must ensure that `bytes_written` bytes of data have actually been written - /// into the chunks of memory, sequentially from the start. + /// into the slices of memory returned from `iter_slices_mut()`, sequentially from the start. #[expect(clippy::missing_panics_doc, reason = "only unreachable panics")] pub unsafe fn commit(self, bytes_written: usize) { - assert!(bytes_written <= self.builder.remaining_mut()); + debug_assert!(bytes_written <= self.buf.remaining_capacity()); if let Some(max_len) = self.max_len { - assert!(bytes_written <= max_len); + debug_assert!(bytes_written <= max_len); } // Ordinarily, we have a type invariant that only the first span builder may contain data, // with the others being spare capacity. For the duration of a vectored write, this // invariant is suspended (because the vectored write has an exclusive reference which makes // the suspension of this invariant invisible to any other caller). We must now restore this - // invariant. We do this by advancing the write head chunk by chunk, triggering the normal + // invariant. We do this by advancing the write head slice by slice, triggering the normal // freezing logic as we go (to avoid implementing two versions of the same logic), until we // have run out of written bytes to commit. @@ -707,17 +737,17 @@ impl BytesBufVectoredWrite<'_> { while bytes_remaining > 0 { let span_builder = self - .builder + .buf .span_builders_reversed .last_mut() .expect("there must be at least one span builder because we still have filled capacity remaining to freeze"); - let bytes_available = span_builder.remaining_mut(); + let bytes_available = span_builder.remaining_capacity(); let bytes_to_commit = bytes_available.min(bytes_remaining); // SAFETY: We forward the promise from our own safety requirements to guarantee that // the specified number of bytes really has been written. - unsafe { self.builder.advance_mut(bytes_to_commit) }; + unsafe { self.buf.advance(bytes_to_commit) }; bytes_remaining = bytes_remaining .checked_sub(bytes_to_commit) @@ -726,11 +756,13 @@ impl BytesBufVectoredWrite<'_> { } } -/// Iterates over the available capacity of a sequence builder as part of a vectored write +/// Iterates over the available capacity of a `BytesBuf` as part of a vectored write /// operation, returning a sequence of `MaybeUninit` slices. +/// +/// The slices may be mutated for as long as the vectored write operation exists. #[derive(Debug)] pub struct BytesBufAvailableIterator<'a> { - builder: &'a mut BytesBuf, + buf: &'a mut BytesBuf, next_span_builder_index: Option, // Self-imposed constraint on how much of the available capacity is made visible through @@ -748,33 +780,31 @@ impl<'a> Iterator for BytesBufAvailableIterator<'a> { let next_span_builder_index = self.next_span_builder_index?; self.next_span_builder_index = Some( - next_span_builder_index - .checked_add(1) - .expect("usize overflow is inconceivable here"), + // Will not overflow because `capacity <= usize::MAX` is a type invariant, + // so the count of span builders certainly cannot be greater. + next_span_builder_index.wrapping_add(1), ); - if self.next_span_builder_index == Some(self.builder.span_builders_reversed.len()) { + if self.next_span_builder_index == Some(self.buf.span_builders_reversed.len()) { self.next_span_builder_index = None; } // The iterator iterates through things in content order but we need to access // the span builders in storage order. let next_span_builder_index_storage_order = self - .builder + .buf .span_builders_reversed .len() - .checked_sub(next_span_builder_index + 1) - .expect("usize overflow is inconceivable here"); + // Will not overflow because `capacity <= usize::MAX` is a type invariant, + // so the count of span builders certainly cannot be greater. + .wrapping_sub(next_span_builder_index + 1); let span_builder = self - .builder + .buf .span_builders_reversed .get_mut(next_span_builder_index_storage_order) .expect("iterator cursor referenced a span builder that does not exist"); - // SAFETY: Must treat it as uninitialized. Yeah, we are, obviously. - // Somewhat pointless to have the callee be marked unsafe considering - // it returns a `MaybeUninit` already but okay whatever, we'll play along. - let uninit_slice_mut = unsafe { span_builder.chunk_mut().as_uninit_slice_mut() }; + let uninit_slice_mut = span_builder.unfilled_slice_mut(); // SAFETY: There is nothing Rust can do to promise the reference we return is valid for 'a // but we can make such a promise ourselves. In essence, returning the references with 'a @@ -782,7 +812,6 @@ impl<'a> Iterator for BytesBufAvailableIterator<'a> { // references are dropped, even if the iterator itself is dropped earlier. We can do this // because we know that to access the chunks requires a reference to the `BytesBuf`, // so as long as a chunk reference exists, access via the `BytesBuf` is blocked. - // TODO: It would be good to have a (ui) test to verify this. let uninit_slice_mut = unsafe { mem::transmute::<&mut [MaybeUninit], &'a mut [MaybeUninit]>(&mut *uninit_slice_mut) }; let uninit_slice_mut = if let Some(max_len) = self.max_len { @@ -793,7 +822,8 @@ impl<'a> Iterator for BytesBufAvailableIterator<'a> { let adjusted_slice = uninit_slice_mut.get_mut(..constrained_len).expect("guarded by min() above"); - self.max_len = Some(max_len.checked_sub(constrained_len).expect("guarded by min() above")); + // Will not wrap because it is guarded by min() above. + self.max_len = Some(max_len.wrapping_sub(constrained_len)); if self.max_len == Some(0) { // Even if there are more span builders, we have returned all the capacity @@ -812,12 +842,13 @@ impl<'a> Iterator for BytesBufAvailableIterator<'a> { impl From for BytesBuf { fn from(value: BytesView) -> Self { - let mut sb = Self::new(); - sb.append(value); - sb + let mut buf = Self::new(); + buf.append(value); + buf } } +#[cfg_attr(coverage_nightly, coverage(off))] #[cfg(test)] mod tests { #![allow(clippy::indexing_slicing, reason = "Fine in test code, we prefer panic on error")] @@ -836,624 +867,623 @@ mod tests { const TWO_U64_SIZE: usize = size_of::() + size_of::(); const THREE_U64_SIZE: usize = size_of::() + size_of::() + size_of::(); + assert_impl_all!(BytesBuf: Send, Sync); + #[test] fn smoke_test() { let memory = FixedBlockTestMemory::new(nz!(1234)); let min_length = 1000; - let mut builder = memory.reserve(min_length); + let mut buf = memory.reserve(min_length); - assert!(builder.capacity() >= min_length); - assert!(builder.remaining_mut() >= min_length); - assert!(builder.is_empty()); - assert_eq!(builder.capacity(), builder.remaining_mut()); - assert_eq!(builder.len(), 0); + assert!(buf.capacity() >= min_length); + assert!(buf.remaining_capacity() >= min_length); + assert_eq!(buf.capacity(), buf.remaining_capacity()); + assert_eq!(buf.len(), 0); + assert!(buf.is_empty()); - builder.put_u64(1234); - builder.put_u64(5678); - builder.put_u64(1234); - builder.put_u64(5678); + buf.put_num_ne(1234_u64); + buf.put_num_ne(5678_u64); + buf.put_num_ne(1234_u64); + buf.put_num_ne(5678_u64); - assert_eq!(builder.len(), 32); - assert!(!builder.is_empty()); + assert_eq!(buf.len(), 32); + assert!(!buf.is_empty()); // SAFETY: Writing 0 bytes is always valid. unsafe { - builder.advance_mut(0); + buf.advance(0); } - let mut first16 = builder.consume(TWO_U64_SIZE); - let mut second16 = builder.consume(TWO_U64_SIZE); + let mut first_two = buf.consume(TWO_U64_SIZE); + let mut second_two = buf.consume(TWO_U64_SIZE); - assert_eq!(first16.len(), 16); - assert_eq!(second16.len(), 16); - assert_eq!(builder.len(), 0); + assert_eq!(first_two.len(), 16); + assert_eq!(second_two.len(), 16); + assert_eq!(buf.len(), 0); - assert_eq!(first16.get_u64(), 1234); - assert_eq!(first16.get_u64(), 5678); + assert_eq!(first_two.get_num_ne::(), 1234); + assert_eq!(first_two.get_num_ne::(), 5678); - assert_eq!(second16.get_u64(), 1234); - assert_eq!(second16.get_u64(), 5678); + assert_eq!(second_two.get_num_ne::(), 1234); + assert_eq!(second_two.get_num_ne::(), 5678); - builder.put_u64(1111); + buf.put_num_ne(1111_u64); - assert_eq!(builder.len(), 8); + assert_eq!(buf.len(), 8); - let mut last8 = builder.consume(U64_SIZE); + let mut last = buf.consume(U64_SIZE); - assert_eq!(last8.len(), 8); - assert_eq!(builder.len(), 0); + assert_eq!(last.len(), 8); + assert_eq!(buf.len(), 0); - assert_eq!(last8.get_u64(), 1111); + assert_eq!(last.get_num_ne::(), 1111); - assert!(builder.consume_checked(1).is_none()); - - assert!(builder.consume_all().is_empty()); + assert!(buf.consume_checked(1).is_none()); + assert!(buf.consume_all().is_empty()); } #[test] - fn extend() { - let mut builder = BytesBuf::new(); + fn extend_capacity() { + let mut buf = BytesBuf::new(); - assert_eq!(builder.capacity(), 0); - assert_eq!(builder.remaining_mut(), 0); + assert_eq!(buf.capacity(), 0); + assert_eq!(buf.remaining_capacity(), 0); let memory = FixedBlockTestMemory::new(nz!(100)); // Have 0, desired 10, requesting 10, will get 100. - builder.reserve(10, &memory); + buf.reserve(10, &memory); - assert_eq!(builder.capacity(), 100); - assert_eq!(builder.remaining_mut(), 100); + assert_eq!(buf.capacity(), 100); + assert_eq!(buf.remaining_capacity(), 100); // Write 10 bytes of data just to verify that it does not affect "capacity" logic. - builder.put_u64(1234); - builder.put_u16(5678); + buf.put_num_ne(1234_u64); + buf.put_num_ne(5678_u16); - assert_eq!(builder.len(), 10); - assert_eq!(builder.remaining_mut(), 90); - assert_eq!(builder.capacity(), 100); + assert_eq!(buf.len(), 10); + assert_eq!(buf.remaining_capacity(), 90); + assert_eq!(buf.capacity(), 100); // Have 100, desired 10+140=150, requesting 50, will get another 100 for a total of 200. - builder.reserve(140, &memory); + buf.reserve(140, &memory); - assert_eq!(builder.len(), 10); - assert_eq!(builder.remaining_mut(), 190); - assert_eq!(builder.capacity(), 200); + assert_eq!(buf.len(), 10); + assert_eq!(buf.remaining_capacity(), 190); + assert_eq!(buf.capacity(), 200); // Have 200, desired 10+200=210, 210-200=10, will get another 100. - builder.reserve(200, &memory); + buf.reserve(200, &memory); - assert_eq!(builder.len(), 10); - assert_eq!(builder.remaining_mut(), 290); - assert_eq!(builder.capacity(), 300); + assert_eq!(buf.len(), 10); + assert_eq!(buf.remaining_capacity(), 290); + assert_eq!(buf.capacity(), 300); } #[test] - fn append() { + fn append_existing_view() { let memory = FixedBlockTestMemory::new(nz!(1234)); let min_length = 1000; - let mut builder1 = memory.reserve(min_length); - let mut builder2 = memory.reserve(min_length); + // This one we use to prepare some data to append. + let mut payload_buffer = memory.reserve(min_length); + + // This is where we append the data to. + let mut target_buffer = memory.reserve(min_length); // First we make a couple pieces to append. - builder1.put_u64(1111); - builder1.put_u64(2222); - builder1.put_u64(3333); - builder1.put_u64(4444); + payload_buffer.put_num_ne(1111_u64); + payload_buffer.put_num_ne(2222_u64); + payload_buffer.put_num_ne(3333_u64); + payload_buffer.put_num_ne(4444_u64); - let to_append1 = builder1.consume(TWO_U64_SIZE); - let to_append2 = builder1.consume(TWO_U64_SIZE); + let payload1 = payload_buffer.consume(TWO_U64_SIZE); + let payload2 = payload_buffer.consume(TWO_U64_SIZE); // Then we prefill some data to start us off. - builder2.put_u64(5555); - builder2.put_u64(6666); + target_buffer.put_num_ne(5555_u64); + target_buffer.put_num_ne(6666_u64); // Consume a little just for extra complexity. - let _ = builder2.consume(U64_SIZE); + let _ = target_buffer.consume(U64_SIZE); - // Append the pieces. - builder2.append(to_append1); - builder2.append(to_append2); + // Append the payloads. + target_buffer.put_bytes(payload1); + target_buffer.put_bytes(payload2); - // Appending an empty sequence does nothing. - builder2.append(BytesView::default()); + // Appending an empty byte sequence does nothing. + target_buffer.put_bytes(BytesView::default()); // Add some custom data at the end. - builder2.put_u64(7777); + target_buffer.put_num_ne(7777_u64); - assert_eq!(builder2.len(), 48); + assert_eq!(target_buffer.len(), 48); - let mut result = builder2.consume(48); + let mut result = target_buffer.consume(48); - assert_eq!(result.get_u64(), 6666); - assert_eq!(result.get_u64(), 1111); - assert_eq!(result.get_u64(), 2222); - assert_eq!(result.get_u64(), 3333); - assert_eq!(result.get_u64(), 4444); - assert_eq!(result.get_u64(), 7777); + assert_eq!(result.get_num_ne::(), 6666); + assert_eq!(result.get_num_ne::(), 1111); + assert_eq!(result.get_num_ne::(), 2222); + assert_eq!(result.get_num_ne::(), 3333); + assert_eq!(result.get_num_ne::(), 4444); + assert_eq!(result.get_num_ne::(), 7777); } #[test] fn consume_all_mixed() { - let mut builder = BytesBuf::new(); + let mut buf = BytesBuf::new(); let memory = FixedBlockTestMemory::new(nz!(8)); // Reserve some capacity and add initial data. - builder.reserve(16, &memory); - builder.put_u64(1111); - builder.put_u64(2222); + buf.reserve(16, &memory); + buf.put_num_ne(1111_u64); + buf.put_num_ne(2222_u64); // Consume some data (the 1111). - let _ = builder.consume(8); + let _ = buf.consume(8); // Append a sequence (the 3333). - let mut append_builder = BytesBuf::new(); - append_builder.reserve(8, &memory); - append_builder.put_u64(3333); - let sequence = append_builder.consume_all(); - builder.append(sequence); + let mut append_buf = BytesBuf::new(); + append_buf.reserve(8, &memory); + append_buf.put_num_ne(3333_u64); + let reused_bytes_to_append = append_buf.consume_all(); + buf.append(reused_bytes_to_append); // Add more data (the 4444). - builder.reserve(8, &memory); - builder.put_u64(4444); + buf.reserve(8, &memory); + buf.put_num_ne(4444_u64); // Consume all data and validate we got all the pieces. - let mut result = builder.consume_all(); + let mut result = buf.consume_all(); assert_eq!(result.len(), 24); - assert_eq!(result.get_u64(), 2222); - assert_eq!(result.get_u64(), 3333); - assert_eq!(result.get_u64(), 4444); + assert_eq!(result.get_num_ne::(), 2222); + assert_eq!(result.get_num_ne::(), 3333); + assert_eq!(result.get_num_ne::(), 4444); } #[test] #[expect(clippy::cognitive_complexity, reason = "test code")] - fn inspect_basic() { - let mut builder = BytesBuf::new(); + fn peek_basic() { + let mut buf = BytesBuf::new(); - assert_eq!(builder.capacity(), 0); - assert_eq!(builder.remaining_mut(), 0); + assert_eq!(buf.capacity(), 0); + assert_eq!(buf.remaining_capacity(), 0); let memory = FixedBlockTestMemory::new(nz!(10)); - // Peeking an empty builder is fine, it is just an empty BytesView in that case. - let peeked = builder.peek(); - assert_eq!(peeked.remaining(), 0); + // Peeking an empty buffer is fine, it is just an empty BytesView in that case. + let peeked = buf.peek(); + assert_eq!(peeked.len(), 0); - builder.reserve(100, &memory); + buf.reserve(100, &memory); - assert_eq!(builder.capacity(), 100); + assert_eq!(buf.capacity(), 100); - builder.put_u64(1111); + buf.put_num_ne(1111_u64); // We have 0 frozen spans and 10 span builders, // the first of which has 8 bytes of filled content. - let mut peeked = builder.peek(); - assert_eq!(peeked.chunk().len(), 8); - assert_eq!(peeked.get_u64(), 1111); - assert_eq!(peeked.remaining(), 0); - - builder.put_u64(2222); - builder.put_u64(3333); - builder.put_u64(4444); - builder.put_u64(5555); - builder.put_u64(6666); - builder.put_u64(7777); - builder.put_u64(8888); + let mut peeked = buf.peek(); + assert_eq!(peeked.first_slice().len(), 8); + assert_eq!(peeked.get_num_ne::(), 1111); + assert_eq!(peeked.len(), 0); + + buf.put_num_ne(2222_u64); + buf.put_num_ne(3333_u64); + buf.put_num_ne(4444_u64); + buf.put_num_ne(5555_u64); + buf.put_num_ne(6666_u64); + buf.put_num_ne(7777_u64); + buf.put_num_ne(8888_u64); // These will cross a span boundary so we can also observe // crossing that boundary during peeking. - builder.put_bytes(9, 8); + buf.put_byte_repeated(9, 8); - assert_eq!(builder.len(), 72); - assert_eq!(builder.capacity(), 100); - assert_eq!(builder.remaining_mut(), 28); + assert_eq!(buf.len(), 72); + assert_eq!(buf.capacity(), 100); + assert_eq!(buf.remaining_capacity(), 28); // We should have 7 frozen spans and 3 span builders, // the first of which has 2 bytes of filled content. - let mut peeked = builder.peek(); + let mut peeked = buf.peek(); - assert_eq!(peeked.remaining(), 72); + assert_eq!(peeked.len(), 72); // This should be the first frozen span of 10 bytes. - assert_eq!(peeked.chunk().len(), 10); + assert_eq!(peeked.first_slice().len(), 10); - assert_eq!(peeked.get_u64(), 1111); - assert_eq!(peeked.get_u64(), 2222); + assert_eq!(peeked.get_num_ne::(), 1111); + assert_eq!(peeked.get_num_ne::(), 2222); - // The length of the sequence builder does not change just because we peek at its data. - assert_eq!(builder.len(), 72); + // The length of the buffer does not change just because we peek at its data. + assert_eq!(buf.len(), 72); // We consumed 16 bytes from the peeked view, so should be looking at the remaining 4 bytes in the 2nd span. - assert_eq!(peeked.chunk().len(), 4); + assert_eq!(peeked.first_slice().len(), 4); - assert_eq!(peeked.get_u64(), 3333); - assert_eq!(peeked.get_u64(), 4444); - assert_eq!(peeked.get_u64(), 5555); - assert_eq!(peeked.get_u64(), 6666); - assert_eq!(peeked.get_u64(), 7777); - assert_eq!(peeked.get_u64(), 8888); + assert_eq!(peeked.get_num_ne::(), 3333); + assert_eq!(peeked.get_num_ne::(), 4444); + assert_eq!(peeked.get_num_ne::(), 5555); + assert_eq!(peeked.get_num_ne::(), 6666); + assert_eq!(peeked.get_num_ne::(), 7777); + assert_eq!(peeked.get_num_ne::(), 8888); for _ in 0..8 { - assert_eq!(peeked.get_u8(), 9); + assert_eq!(peeked.get_byte(), 9); } - assert_eq!(peeked.remaining(), 0); - - // Reading 0 bytes is always valid. - peeked.advance(0); - - assert_eq!(peeked.chunk().len(), 0); + assert_eq!(peeked.len(), 0); + assert_eq!(peeked.first_slice().len(), 0); // Fill up the remaining 28 bytes of data so we have a full sequence builder. - builder.put_bytes(88, 28); + buf.put_byte_repeated(88, 28); - let mut peeked = builder.peek(); + let mut peeked = buf.peek(); peeked.advance(72); - assert_eq!(peeked.remaining(), 28); + assert_eq!(peeked.len(), 28); for _ in 0..28 { - assert_eq!(peeked.get_u8(), 88); + assert_eq!(peeked.get_byte(), 88); } } #[test] fn consume_part_of_frozen_span() { - let mut builder = BytesBuf::new(); + let mut buf = BytesBuf::new(); - assert_eq!(builder.capacity(), 0); - assert_eq!(builder.remaining_mut(), 0); + assert_eq!(buf.capacity(), 0); + assert_eq!(buf.remaining_capacity(), 0); let memory = FixedBlockTestMemory::new(nz!(10)); - builder.reserve(100, &memory); + buf.reserve(100, &memory); - assert_eq!(builder.capacity(), 100); + assert_eq!(buf.capacity(), 100); - builder.put_u64(1111); + buf.put_num_ne(1111_u64); // This freezes the first span of 10, as we filled it all up. - builder.put_u64(2222); + buf.put_num_ne(2222_u64); - let mut first8 = builder.consume(U64_SIZE); - assert_eq!(first8.get_u64(), 1111); + let mut first8 = buf.consume(U64_SIZE); + assert_eq!(first8.get_num_ne::(), 1111); assert!(first8.is_empty()); - builder.put_u64(3333); + buf.put_num_ne(3333_u64); - let mut second16 = builder.consume(16); - assert_eq!(second16.get_u64(), 2222); - assert_eq!(second16.get_u64(), 3333); + let mut second16 = buf.consume(16); + assert_eq!(second16.get_num_ne::(), 2222); + assert_eq!(second16.get_num_ne::(), 3333); assert!(second16.is_empty()); } #[test] - fn empty_builder() { - let mut builder = BytesBuf::new(); - assert!(builder.is_empty()); - assert!(!builder.peek().has_remaining()); - assert_eq!(0, builder.chunk_mut().len()); + fn empty_buffer() { + let mut buf = BytesBuf::new(); + assert!(buf.is_empty()); + assert!(buf.peek().is_empty()); + assert_eq!(0, buf.first_unfilled_slice().len()); - let consumed = builder.consume(0); + let consumed = buf.consume(0); assert!(consumed.is_empty()); - let consumed = builder.consume_all(); + let consumed = buf.consume_all(); assert!(consumed.is_empty()); } - #[test] - fn thread_safe_type() { - assert_impl_all!(BytesBuf: Send, Sync); - } - #[test] fn iter_available_empty_with_capacity() { - let mut builder = BytesBuf::new(); + let mut buf = BytesBuf::new(); - assert_eq!(builder.capacity(), 0); - assert_eq!(builder.remaining_mut(), 0); + assert_eq!(buf.capacity(), 0); + assert_eq!(buf.remaining_capacity(), 0); let memory = FixedBlockTestMemory::new(nz!(100)); // Capacity: 0 -> 1000 (10x100) - builder.reserve(1000, &memory); + buf.reserve(1000, &memory); - assert_eq!(builder.capacity(), 1000); - assert_eq!(builder.remaining_mut(), 1000); + assert_eq!(buf.capacity(), 1000); + assert_eq!(buf.remaining_capacity(), 1000); - let iter = builder.iter_available_capacity(None); + let iter = buf.iter_available_capacity(None); - // Demonstrating that we can access chunks concurrently, not only one by one. - let chunks = iter.collect::>(); + // Demonstrating that we can access slices concurrently, not only one by one. + let slices = iter.collect::>(); - assert_eq!(chunks.len(), 10); + assert_eq!(slices.len(), 10); - for chunk in chunks { - assert_eq!(chunk.len(), 100); + for slice in slices { + assert_eq!(slice.len(), 100); } - // After we have dropped all chunk references, it is again legal to access the builder. - // This is blocked by the borrow checker while chunk references still exist. - builder.reserve(100, &memory); + // After we have dropped all slice references, it is again legal to access the buffer. + // This is blocked by the borrow checker while slice references still exist. + buf.reserve(100, &memory); } #[test] fn iter_available_nonempty() { - let mut builder = BytesBuf::new(); + let mut buf = BytesBuf::new(); - assert_eq!(builder.capacity(), 0); - assert_eq!(builder.remaining_mut(), 0); + assert_eq!(buf.capacity(), 0); + assert_eq!(buf.remaining_capacity(), 0); let memory = FixedBlockTestMemory::new(nz!(8)); // Capacity: 0 -> 16 (2x8) - builder.reserve(TWO_U64_SIZE, &memory); + buf.reserve(TWO_U64_SIZE, &memory); - assert_eq!(builder.capacity(), 16); - assert_eq!(builder.remaining_mut(), 16); + assert_eq!(buf.capacity(), 16); + assert_eq!(buf.remaining_capacity(), 16); // We write an u64 - this fills half the capacity and should result in // the first span builder being frozen and the second remaining in its entirety. - builder.put_u64(1234); + buf.put_num_ne(1234_u64); - assert_eq!(builder.len(), 8); - assert_eq!(builder.remaining_mut(), 8); + assert_eq!(buf.len(), 8); + assert_eq!(buf.remaining_capacity(), 8); - let available_chunks = builder.iter_available_capacity(None).collect::>(); - assert_eq!(available_chunks.len(), 1); - assert_eq!(available_chunks[0].len(), 8); + let available_slices = buf.iter_available_capacity(None).collect::>(); + assert_eq!(available_slices.len(), 1); + assert_eq!(available_slices[0].len(), 8); // We write a u32 - this fills half the remaining capacity, which results - // in a half-filled span builder remaining in the sequence builder. - builder.put_u32(5678); + // in a half-filled span builder remaining in the buffer. + buf.put_num_ne(5678_u32); - assert_eq!(builder.len(), 12); - assert_eq!(builder.remaining_mut(), 4); + assert_eq!(buf.len(), 12); + assert_eq!(buf.remaining_capacity(), 4); - let available_chunks = builder.iter_available_capacity(None).collect::>(); - assert_eq!(available_chunks.len(), 1); - assert_eq!(available_chunks[0].len(), 4); + let available_slices = buf.iter_available_capacity(None).collect::>(); + assert_eq!(available_slices.len(), 1); + assert_eq!(available_slices[0].len(), 4); // We write a final u32 to use up all the capacity. - builder.put_u32(9012); + buf.put_num_ne(9012_u32); - assert_eq!(builder.len(), 16); - assert_eq!(builder.remaining_mut(), 0); + assert_eq!(buf.len(), 16); + assert_eq!(buf.remaining_capacity(), 0); - assert_eq!(builder.iter_available_capacity(None).count(), 0); + assert_eq!(buf.iter_available_capacity(None).count(), 0); } #[test] fn iter_available_empty_no_capacity() { - let mut builder = BytesBuf::new(); + let mut buf = BytesBuf::new(); - assert_eq!(builder.capacity(), 0); - assert_eq!(builder.remaining_mut(), 0); - assert_eq!(builder.iter_available_capacity(None).count(), 0); + assert_eq!(buf.capacity(), 0); + assert_eq!(buf.remaining_capacity(), 0); + assert_eq!(buf.iter_available_capacity(None).count(), 0); } #[test] fn vectored_write_zero() { - let mut builder = BytesBuf::new(); + let mut buf = BytesBuf::new(); - assert_eq!(builder.capacity(), 0); - assert_eq!(builder.remaining_mut(), 0); + assert_eq!(buf.capacity(), 0); + assert_eq!(buf.remaining_capacity(), 0); let memory = FixedBlockTestMemory::new(nz!(8)); // Capacity: 0 -> 16 (2x8) - builder.reserve(TWO_U64_SIZE, &memory); + buf.reserve(TWO_U64_SIZE, &memory); - assert_eq!(builder.capacity(), 16); - assert_eq!(builder.remaining_mut(), 16); + assert_eq!(buf.capacity(), 16); + assert_eq!(buf.remaining_capacity(), 16); - let vectored_write = builder.begin_vectored_write(None); + let vectored_write = buf.begin_vectored_write(None); // SAFETY: Yes, we really wrote 0 bytes. unsafe { vectored_write.commit(0); } - assert_eq!(builder.capacity(), 16); - assert_eq!(builder.remaining_mut(), 16); + assert_eq!(buf.capacity(), 16); + assert_eq!(buf.remaining_capacity(), 16); } #[test] - fn vectored_write_one_chunk() { - let mut builder = BytesBuf::new(); + fn vectored_write_one_slice() { + let mut buf = BytesBuf::new(); - assert_eq!(builder.capacity(), 0); - assert_eq!(builder.remaining_mut(), 0); + assert_eq!(buf.capacity(), 0); + assert_eq!(buf.remaining_capacity(), 0); let memory = FixedBlockTestMemory::new(nz!(8)); // Capacity: 0 -> 8 (1x8) - builder.reserve(U64_SIZE, &memory); + buf.reserve(U64_SIZE, &memory); - assert_eq!(builder.capacity(), 8); - assert_eq!(builder.remaining_mut(), 8); + assert_eq!(buf.capacity(), 8); + assert_eq!(buf.remaining_capacity(), 8); - let mut vectored_write = builder.begin_vectored_write(None); + let mut vectored_write = buf.begin_vectored_write(None); - let mut chunks = vectored_write.iter_chunks_mut().collect::>(); - assert_eq!(chunks.len(), 1); - assert_eq!(chunks[0].len(), 8); + let mut slices = vectored_write.iter_slices_mut().collect::>(); + assert_eq!(slices.len(), 1); + assert_eq!(slices[0].len(), 8); - chunks[0].put_u64(0x3333_3333_3333_3333); + write_copy_of_slice(slices[0], &0x3333_3333_3333_3333_u64.to_ne_bytes()); // SAFETY: Yes, we really wrote 8 bytes. unsafe { vectored_write.commit(8); } - assert_eq!(builder.len(), 8); - assert_eq!(builder.remaining_mut(), 0); - assert_eq!(builder.capacity(), 8); + assert_eq!(buf.len(), 8); + assert_eq!(buf.remaining_capacity(), 0); + assert_eq!(buf.capacity(), 8); - let mut result = builder.consume(U64_SIZE); - assert_eq!(result.get_u64(), 0x3333_3333_3333_3333); + let mut result = buf.consume(U64_SIZE); + assert_eq!(result.get_num_ne::(), 0x3333_3333_3333_3333); } #[test] - fn vectored_write_multiple_chunks() { - let mut builder = BytesBuf::new(); + fn vectored_write_multiple_slices() { + let mut buf = BytesBuf::new(); - assert_eq!(builder.capacity(), 0); - assert_eq!(builder.remaining_mut(), 0); + assert_eq!(buf.capacity(), 0); + assert_eq!(buf.remaining_capacity(), 0); let memory = FixedBlockTestMemory::new(nz!(8)); // Capacity: 0 -> 24 (3x8) - builder.reserve(THREE_U64_SIZE, &memory); + buf.reserve(THREE_U64_SIZE, &memory); - assert_eq!(builder.capacity(), 24); - assert_eq!(builder.remaining_mut(), 24); + assert_eq!(buf.capacity(), 24); + assert_eq!(buf.remaining_capacity(), 24); - let mut vectored_write = builder.begin_vectored_write(None); + let mut vectored_write = buf.begin_vectored_write(None); - let mut chunks = vectored_write.iter_chunks_mut().collect::>(); - assert_eq!(chunks.len(), 3); - assert_eq!(chunks[0].len(), 8); - assert_eq!(chunks[1].len(), 8); - assert_eq!(chunks[2].len(), 8); + let mut slices = vectored_write.iter_slices_mut().collect::>(); + assert_eq!(slices.len(), 3); + assert_eq!(slices[0].len(), 8); + assert_eq!(slices[1].len(), 8); + assert_eq!(slices[2].len(), 8); // We fill 12 bytes, leaving middle chunk split in half between filled/available. - chunks[0].put_u64(0x3333_3333_3333_3333); - chunks[1].put_u32(0x4444_4444); + + write_copy_of_slice(slices[0], &0x3333_3333_3333_3333_u64.to_ne_bytes()); + write_copy_of_slice(slices[1], &0x4444_4444_u32.to_ne_bytes()); // SAFETY: Yes, we really wrote 12 bytes. unsafe { vectored_write.commit(12); } - assert_eq!(builder.len(), 12); - assert_eq!(builder.remaining_mut(), 12); - assert_eq!(builder.capacity(), 24); + assert_eq!(buf.len(), 12); + assert_eq!(buf.remaining_capacity(), 12); + assert_eq!(buf.capacity(), 24); + + let mut vectored_write = buf.begin_vectored_write(None); - let mut vectored_write = builder.begin_vectored_write(None); + let mut slices = vectored_write.iter_slices_mut().collect::>(); + assert_eq!(slices.len(), 2); + assert_eq!(slices[0].len(), 4); + assert_eq!(slices[1].len(), 8); - let mut chunks = vectored_write.iter_chunks_mut().collect::>(); - assert_eq!(chunks.len(), 2); - assert_eq!(chunks[0].len(), 4); - assert_eq!(chunks[1].len(), 8); + // We fill the remaining 12 bytes. - chunks[0].put_u32(0x5555_5555); - chunks[1].put_u64(0x6666_6666_6666_6666); + write_copy_of_slice(slices[0], &0x5555_5555_u32.to_ne_bytes()); + write_copy_of_slice(slices[1], &0x6666_6666_6666_6666_u64.to_ne_bytes()); // SAFETY: Yes, we really wrote 12 bytes. unsafe { vectored_write.commit(12); } - assert_eq!(builder.len(), 24); - assert_eq!(builder.remaining_mut(), 0); - assert_eq!(builder.capacity(), 24); + assert_eq!(buf.len(), 24); + assert_eq!(buf.remaining_capacity(), 0); + assert_eq!(buf.capacity(), 24); - let mut result = builder.consume(THREE_U64_SIZE); - assert_eq!(result.get_u64(), 0x3333_3333_3333_3333); - assert_eq!(result.get_u32(), 0x4444_4444); - assert_eq!(result.get_u32(), 0x5555_5555); - assert_eq!(result.get_u64(), 0x6666_6666_6666_6666); + let mut result = buf.consume(THREE_U64_SIZE); + assert_eq!(result.get_num_ne::(), 0x3333_3333_3333_3333); + assert_eq!(result.get_num_ne::(), 0x4444_4444); + assert_eq!(result.get_num_ne::(), 0x5555_5555); + assert_eq!(result.get_num_ne::(), 0x6666_6666_6666_6666); } #[test] fn vectored_write_max_len() { - let mut builder = BytesBuf::new(); + let mut buf = BytesBuf::new(); - assert_eq!(builder.capacity(), 0); - assert_eq!(builder.remaining_mut(), 0); + assert_eq!(buf.capacity(), 0); + assert_eq!(buf.remaining_capacity(), 0); let memory = FixedBlockTestMemory::new(nz!(8)); // Capacity: 0 -> 24 (3x8) - builder.reserve(THREE_U64_SIZE, &memory); + buf.reserve(THREE_U64_SIZE, &memory); - assert_eq!(builder.capacity(), 24); - assert_eq!(builder.remaining_mut(), 24); + assert_eq!(buf.capacity(), 24); + assert_eq!(buf.remaining_capacity(), 24); // We limit to 13 bytes of visible capacity, of which we will fill 12. - let mut vectored_write = builder.begin_vectored_write(Some(13)); + let mut vectored_write = buf.begin_vectored_write(Some(13)); - let mut chunks = vectored_write.iter_chunks_mut().collect::>(); - assert_eq!(chunks.len(), 2); - assert_eq!(chunks[0].len(), 8); - assert_eq!(chunks[1].len(), 5); + let mut slices = vectored_write.iter_slices_mut().collect::>(); + assert_eq!(slices.len(), 2); + assert_eq!(slices[0].len(), 8); + assert_eq!(slices[1].len(), 5); // We fill 12 bytes, leaving middle chunk split in half between filled/available. - chunks[0].put_u64(0x3333_3333_3333_3333); - chunks[1].put_u32(0x4444_4444); + + write_copy_of_slice(slices[0], &0x3333_3333_3333_3333_u64.to_ne_bytes()); + write_copy_of_slice(slices[1], &0x4444_4444_u32.to_ne_bytes()); // SAFETY: Yes, we really wrote 12 bytes. unsafe { vectored_write.commit(12); } - assert_eq!(builder.len(), 12); - assert_eq!(builder.remaining_mut(), 12); - assert_eq!(builder.capacity(), 24); + assert_eq!(buf.len(), 12); + assert_eq!(buf.remaining_capacity(), 12); + assert_eq!(buf.capacity(), 24); // There are 12 remaining and we set max_limit to exactly cover those 12 - let mut vectored_write = builder.begin_vectored_write(Some(12)); + let mut vectored_write = buf.begin_vectored_write(Some(12)); - let mut chunks = vectored_write.iter_chunks_mut().collect::>(); - assert_eq!(chunks.len(), 2); - assert_eq!(chunks[0].len(), 4); - assert_eq!(chunks[1].len(), 8); + let mut slices = vectored_write.iter_slices_mut().collect::>(); + assert_eq!(slices.len(), 2); + assert_eq!(slices[0].len(), 4); + assert_eq!(slices[1].len(), 8); - chunks[0].put_u32(0x5555_5555); - chunks[1].put_u64(0x6666_6666_6666_6666); + write_copy_of_slice(slices[0], &0x5555_5555_u32.to_ne_bytes()); + write_copy_of_slice(slices[1], &0x6666_6666_6666_6666_u64.to_ne_bytes()); // SAFETY: Yes, we really wrote 12 bytes. unsafe { vectored_write.commit(12); } - assert_eq!(builder.len(), 24); - assert_eq!(builder.remaining_mut(), 0); - assert_eq!(builder.capacity(), 24); + assert_eq!(buf.len(), 24); + assert_eq!(buf.remaining_capacity(), 0); + assert_eq!(buf.capacity(), 24); - let mut result = builder.consume(THREE_U64_SIZE); - assert_eq!(result.get_u64(), 0x3333_3333_3333_3333); - assert_eq!(result.get_u32(), 0x4444_4444); - assert_eq!(result.get_u32(), 0x5555_5555); - assert_eq!(result.get_u64(), 0x6666_6666_6666_6666); + let mut result = buf.consume(THREE_U64_SIZE); + assert_eq!(result.get_num_ne::(), 0x3333_3333_3333_3333); + assert_eq!(result.get_num_ne::(), 0x4444_4444); + assert_eq!(result.get_num_ne::(), 0x5555_5555); + assert_eq!(result.get_num_ne::(), 0x6666_6666_6666_6666); } #[test] fn vectored_write_max_len_overflow() { - let mut builder = BytesBuf::new(); + let mut buf = BytesBuf::new(); let memory = FixedBlockTestMemory::new(nz!(8)); // Capacity: 0 -> 24 (3x8) - builder.reserve(THREE_U64_SIZE, &memory); + buf.reserve(THREE_U64_SIZE, &memory); - assert_eq!(builder.capacity(), 24); - assert_eq!(builder.remaining_mut(), 24); + assert_eq!(buf.capacity(), 24); + assert_eq!(buf.remaining_capacity(), 24); // We ask for 25 bytes of capacity but there are only 24 available. Oops! - assert_panic!(builder.begin_vectored_write(Some(25))); + assert_panic!(buf.begin_vectored_write(Some(25))); } #[test] fn vectored_write_overcommit() { - let mut builder = BytesBuf::new(); + let mut buf = BytesBuf::new(); - assert_eq!(builder.capacity(), 0); - assert_eq!(builder.remaining_mut(), 0); + assert_eq!(buf.capacity(), 0); + assert_eq!(buf.remaining_capacity(), 0); let memory = FixedBlockTestMemory::new(nz!(8)); // Capacity: 0 -> 16 (2x8) - builder.reserve(TWO_U64_SIZE, &memory); + buf.reserve(TWO_U64_SIZE, &memory); - assert_eq!(builder.capacity(), 16); - assert_eq!(builder.remaining_mut(), 16); + assert_eq!(buf.capacity(), 16); + assert_eq!(buf.remaining_capacity(), 16); - let vectored_write = builder.begin_vectored_write(None); + let vectored_write = buf.begin_vectored_write(None); assert_panic!( // SAFETY: Intentionally lying here to trigger a panic. @@ -1465,34 +1495,34 @@ mod tests { #[test] fn vectored_write_abort() { - let mut builder = BytesBuf::new(); + let mut buf = BytesBuf::new(); - assert_eq!(builder.capacity(), 0); - assert_eq!(builder.remaining_mut(), 0); + assert_eq!(buf.capacity(), 0); + assert_eq!(buf.remaining_capacity(), 0); let memory = FixedBlockTestMemory::new(nz!(8)); // Capacity: 0 -> 8 (1x8) - builder.reserve(U64_SIZE, &memory); + buf.reserve(U64_SIZE, &memory); - assert_eq!(builder.capacity(), 8); - assert_eq!(builder.remaining_mut(), 8); + assert_eq!(buf.capacity(), 8); + assert_eq!(buf.remaining_capacity(), 8); - let mut vectored_write = builder.begin_vectored_write(None); + let mut vectored_write = buf.begin_vectored_write(None); - let mut chunks = vectored_write.iter_chunks_mut().collect::>(); - assert_eq!(chunks.len(), 1); - assert_eq!(chunks[0].len(), 8); + let mut slices = vectored_write.iter_slices_mut().collect::>(); + assert_eq!(slices.len(), 1); + assert_eq!(slices[0].len(), 8); - chunks[0].put_u64(0x3333_3333_3333_3333); + write_copy_of_slice(slices[0], &0x3333_3333_3333_3333_u64.to_ne_bytes()); // Actually never mind - we drop it here. #[expect(clippy::drop_non_drop, reason = "Just being explicit for illustration")] drop(vectored_write); - assert_eq!(builder.len(), 0); - assert_eq!(builder.remaining_mut(), 8); - assert_eq!(builder.capacity(), 8); + assert_eq!(buf.len(), 0); + assert_eq!(buf.remaining_capacity(), 8); + assert_eq!(buf.capacity(), 8); } #[test] @@ -1515,15 +1545,15 @@ mod tests { // SAFETY: We guarantee exclusive access to the memory capacity. let block2 = unsafe { block2.as_ref().to_block() }; - let mut builder = BytesBuf::from_blocks([block1, block2]); + let mut buf = BytesBuf::from_blocks([block1, block2]); // Freezes first span of 8, retains one span builder. - builder.put_u64(1234); + buf.put_num_ne(1234_u64); - assert_eq!(builder.frozen_spans.len(), 1); - assert_eq!(builder.span_builders_reversed.len(), 1); + assert_eq!(buf.frozen_spans.len(), 1); + assert_eq!(buf.span_builders_reversed.len(), 1); - builder.extend_lifetime() + buf.extend_lifetime() }; // The sequence builder was destroyed and all BlockRefs it was holding are gone. @@ -1559,15 +1589,15 @@ mod tests { // SAFETY: We guarantee exclusive access to the memory capacity. let block2 = unsafe { block2.as_ref().to_block() }; - let mut builder = BytesBuf::from_blocks([block1, block2]); + let mut buf = BytesBuf::from_blocks([block1, block2]); // Freezes first span of 8, retains one span builder. - builder.put_u64(1234); + buf.put_num_ne(1234_u64); - assert_eq!(builder.frozen_spans.len(), 1); - assert_eq!(builder.span_builders_reversed.len(), 1); + assert_eq!(buf.frozen_spans.len(), 1); + assert_eq!(buf.span_builders_reversed.len(), 1); - let vectored_write = builder.begin_vectored_write(None); + let vectored_write = buf.begin_vectored_write(None); vectored_write.extend_lifetime() }; @@ -1586,65 +1616,65 @@ mod tests { } #[test] - fn from_sequence() { + fn from_view() { let memory = GlobalPool::new(); - let s1 = BytesView::copied_from_slice(b"bla bla bla", &memory); + let view1 = BytesView::copied_from_slice(b"bla bla bla", &memory); - let mut sb: BytesBuf = s1.clone().into(); + let mut buf: BytesBuf = view1.clone().into(); - let s2 = sb.consume_all(); + let view2 = buf.consume_all(); - assert_eq!(s1, s2); + assert_eq!(view1, view2); } #[test] fn consume_manifest_correctly_calculated() { let memory = FixedBlockTestMemory::new(nz!(10)); - let mut builder = BytesBuf::new(); - builder.reserve(100, &memory); + let mut buf = BytesBuf::new(); + buf.reserve(100, &memory); // 32 bytes in 3 spans. - builder.put_u64(1111); - builder.put_u64(1111); - builder.put_u64(1111); - builder.put_u64(1111); + buf.put_num_ne(1111_u64); + buf.put_num_ne(1111_u64); + buf.put_num_ne(1111_u64); + buf.put_num_ne(1111_u64); // Freeze it all - a precondition to consuming is to freeze everything first. - builder.ensure_frozen(32); + buf.ensure_frozen(32); - let consume8 = builder.prepare_consume(8); + let consume8 = buf.prepare_consume(8); assert_eq!(consume8.detach_complete_frozen_spans, 0); assert_eq!(consume8.consume_partial_span_bytes, 8); assert_eq!(consume8.required_spans_capacity(), 1); - let consume10 = builder.prepare_consume(10); + let consume10 = buf.prepare_consume(10); assert_eq!(consume10.detach_complete_frozen_spans, 1); assert_eq!(consume10.consume_partial_span_bytes, 0); assert_eq!(consume10.required_spans_capacity(), 1); - let consume11 = builder.prepare_consume(11); + let consume11 = buf.prepare_consume(11); assert_eq!(consume11.detach_complete_frozen_spans, 1); assert_eq!(consume11.consume_partial_span_bytes, 1); assert_eq!(consume11.required_spans_capacity(), 2); - let consume30 = builder.prepare_consume(30); + let consume30 = buf.prepare_consume(30); assert_eq!(consume30.detach_complete_frozen_spans, 3); assert_eq!(consume30.consume_partial_span_bytes, 0); assert_eq!(consume30.required_spans_capacity(), 3); - let consume31 = builder.prepare_consume(31); + let consume31 = buf.prepare_consume(31); assert_eq!(consume31.detach_complete_frozen_spans, 3); assert_eq!(consume31.consume_partial_span_bytes, 1); assert_eq!(consume31.required_spans_capacity(), 4); - let consume32 = builder.prepare_consume(32); + let consume32 = buf.prepare_consume(32); // Note that even though our memory comes in blocks of 10, there are only 2 bytes // in the last frozen span, for a total frozen of 10 + 10 + 10 + 2. We consume it all. @@ -1662,45 +1692,10 @@ mod tests { assert_eq!(size_of::(), 552); } - #[test] - fn debug_fmt_mixed_state() { - let memory = FixedBlockTestMemory::new(nz!(8)); - - let mut builder = BytesBuf::new(); - - // Reserve 2 blocks (16 bytes). - // State: available=16, span_builders="8, 8" - builder.reserve(16, &memory); - - // Write 12 bytes. - // State: len=12, available=4, span_builders="8 + 0, 4 + 4" - builder.put_u64(0xAAAA_AAAA_AAAA_AAAA); - builder.put_u32(0xBBBB_BBBB); - - // Freeze the 12 bytes. This will create two frozen spans (8 and 4 bytes). - // State: len=12, frozen=12, available=4, frozen_spans="8, 4", span_builders="4" - builder.ensure_frozen(12); - - // Write 2 more bytes into the current span builder. - // State: len=14, frozen=12, available=2, frozen_spans="8, 4", span_builders="2 + 2" - builder.put_u16(0xCCCC); - - // Reserve another block. - // State: len=14, frozen=12, available=10, frozen_spans="8, 4", span_builders="2 + 2, 8" - builder.reserve(8, &memory); - - let debug_string = format!("{builder:?}"); - - assert_eq!( - debug_string, - "BytesBuf { len: 14, frozen: 12, available: 10, frozen_spans: \"8, 4\", span_builders: \"2 + 2, 8\" }" - ); - } - #[test] fn peek_empty_builder() { - let builder = BytesBuf::new(); - let peeked = builder.peek(); + let buf = BytesBuf::new(); + let peeked = buf.peek(); assert!(peeked.is_empty()); assert_eq!(peeked.len(), 0); @@ -1709,163 +1704,169 @@ mod tests { #[test] fn peek_with_frozen_spans_only() { let memory = FixedBlockTestMemory::new(nz!(10)); - let mut builder = BytesBuf::new(); - - builder.reserve(20, &memory); - builder.put_u64(0x1111_1111_1111_1111); - builder.put_u64(0x2222_2222_2222_2222); + let mut buf = BytesBuf::new(); + buf.reserve(20, &memory); + buf.put_num_ne(0x1111_1111_1111_1111_u64); + buf.put_num_ne(0x2222_2222_2222_2222_u64); // Both blocks are now frozen (filled completely) - assert_eq!(builder.len(), 16); + assert_eq!(buf.len(), 16); - let mut peeked = builder.peek(); + let mut peeked = buf.peek(); assert_eq!(peeked.len(), 16); - assert_eq!(peeked.get_u64(), 0x1111_1111_1111_1111); - assert_eq!(peeked.get_u64(), 0x2222_2222_2222_2222); + assert_eq!(peeked.get_num_ne::(), 0x1111_1111_1111_1111); + assert_eq!(peeked.get_num_ne::(), 0x2222_2222_2222_2222); // Original builder still has the data - assert_eq!(builder.len(), 16); + assert_eq!(buf.len(), 16); } #[test] fn peek_with_partially_filled_span_builder() { let memory = FixedBlockTestMemory::new(nz!(10)); - let mut builder = BytesBuf::new(); - - builder.reserve(10, &memory); - builder.put_u64(0x3333_3333_3333_3333); - builder.put_u16(0x4444); + let mut buf = BytesBuf::new(); + buf.reserve(10, &memory); + buf.put_num_ne(0x3333_3333_3333_3333_u64); + buf.put_num_ne(0x4444_u16); // We have 10 bytes filled in a 10-byte block - assert_eq!(builder.len(), 10); + assert_eq!(buf.len(), 10); - let mut peeked = builder.peek(); + let mut peeked = buf.peek(); assert_eq!(peeked.len(), 10); - assert_eq!(peeked.get_u64(), 0x3333_3333_3333_3333); - assert_eq!(peeked.get_u16(), 0x4444); + assert_eq!(peeked.get_num_ne::(), 0x3333_3333_3333_3333); + assert_eq!(peeked.get_num_ne::(), 0x4444); // Original builder still has the data - assert_eq!(builder.len(), 10); + assert_eq!(buf.len(), 10); } #[test] fn peek_preserves_capacity_of_partial_span_builder() { let memory = FixedBlockTestMemory::new(nz!(20)); - let mut builder = BytesBuf::new(); + let mut buf = BytesBuf::new(); - builder.reserve(20, &memory); - builder.put_u64(0x5555_5555_5555_5555); + buf.reserve(20, &memory); + buf.put_num_ne(0x5555_5555_5555_5555_u64); // We have 8 bytes filled and 12 bytes remaining capacity - assert_eq!(builder.len(), 8); - assert_eq!(builder.remaining_mut(), 12); + assert_eq!(buf.len(), 8); + assert_eq!(buf.remaining_capacity(), 12); - let mut peeked = builder.peek(); + let mut peeked = buf.peek(); assert_eq!(peeked.len(), 8); - assert_eq!(peeked.get_u64(), 0x5555_5555_5555_5555); + assert_eq!(peeked.get_num_ne::(), 0x5555_5555_5555_5555); // CRITICAL TEST: Capacity should be preserved - assert_eq!(builder.len(), 8); - assert_eq!(builder.remaining_mut(), 12); + assert_eq!(buf.len(), 8); + assert_eq!(buf.remaining_capacity(), 12); // We should still be able to write more data - builder.put_u32(0x6666_6666); - assert_eq!(builder.len(), 12); - assert_eq!(builder.remaining_mut(), 8); + buf.put_num_ne(0x6666_6666_u32); + assert_eq!(buf.len(), 12); + assert_eq!(buf.remaining_capacity(), 8); // And we can peek again to see the updated data - let mut peeked2 = builder.peek(); + let mut peeked2 = buf.peek(); assert_eq!(peeked2.len(), 12); - assert_eq!(peeked2.get_u64(), 0x5555_5555_5555_5555); - assert_eq!(peeked2.get_u32(), 0x6666_6666); + assert_eq!(peeked2.get_num_ne::(), 0x5555_5555_5555_5555); + assert_eq!(peeked2.get_num_ne::(), 0x6666_6666); } #[test] fn peek_with_mixed_frozen_and_unfrozen() { let memory = FixedBlockTestMemory::new(nz!(10)); - let mut builder = BytesBuf::new(); + let mut buf = BytesBuf::new(); - builder.reserve(30, &memory); + buf.reserve(30, &memory); // Fill first block completely (10 bytes) - will be frozen - builder.put_u64(0x1111_1111_1111_1111); - builder.put_u16(0x2222); + buf.put_num_ne(0x1111_1111_1111_1111_u64); + buf.put_num_ne(0x2222_u16); // Fill second block completely (10 bytes) - will be frozen - builder.put_u64(0x3333_3333_3333_3333); - builder.put_u16(0x4444); + buf.put_num_ne(0x3333_3333_3333_3333_u64); + buf.put_num_ne(0x4444_u16); // Partially fill third block (only 4 bytes) - will remain unfrozen - builder.put_u32(0x5555_5555); + buf.put_num_ne(0x5555_5555_u32); - assert_eq!(builder.len(), 24); - assert_eq!(builder.remaining_mut(), 6); + assert_eq!(buf.len(), 24); + assert_eq!(buf.remaining_capacity(), 6); - let mut peeked = builder.peek(); + let mut peeked = buf.peek(); assert_eq!(peeked.len(), 24); - assert_eq!(peeked.get_u64(), 0x1111_1111_1111_1111); - assert_eq!(peeked.get_u16(), 0x2222); - assert_eq!(peeked.get_u64(), 0x3333_3333_3333_3333); - assert_eq!(peeked.get_u16(), 0x4444); - assert_eq!(peeked.get_u32(), 0x5555_5555); - + assert_eq!(peeked.get_num_ne::(), 0x1111_1111_1111_1111); + assert_eq!(peeked.get_num_ne::(), 0x2222); + assert_eq!(peeked.get_num_ne::(), 0x3333_3333_3333_3333); + assert_eq!(peeked.get_num_ne::(), 0x4444); + assert_eq!(peeked.get_num_ne::(), 0x5555_5555); // Original builder still has all the data and capacity - assert_eq!(builder.len(), 24); - assert_eq!(builder.remaining_mut(), 6); + assert_eq!(buf.len(), 24); + assert_eq!(buf.remaining_capacity(), 6); } #[test] fn peek_then_consume() { let memory = FixedBlockTestMemory::new(nz!(20)); - let mut builder = BytesBuf::new(); + let mut buf = BytesBuf::new(); - builder.reserve(20, &memory); - builder.put_u64(0x7777_7777_7777_7777); - builder.put_u32(0x8888_8888); - - assert_eq!(builder.len(), 12); + buf.reserve(20, &memory); + buf.put_num_ne(0x7777_7777_7777_7777_u64); + buf.put_num_ne(0x8888_8888_u32); + assert_eq!(buf.len(), 12); // Peek at the data - let mut peeked = builder.peek(); + let mut peeked = buf.peek(); assert_eq!(peeked.len(), 12); - assert_eq!(peeked.get_u64(), 0x7777_7777_7777_7777); + assert_eq!(peeked.get_num_ne::(), 0x7777_7777_7777_7777); // Original builder still has the data - assert_eq!(builder.len(), 12); + assert_eq!(buf.len(), 12); // Now consume some of it - let mut consumed = builder.consume(8); - assert_eq!(consumed.get_u64(), 0x7777_7777_7777_7777); + let mut consumed = buf.consume(8); + assert_eq!(consumed.get_num_ne::(), 0x7777_7777_7777_7777); // Builder should have less data now - assert_eq!(builder.len(), 4); + assert_eq!(buf.len(), 4); // Peek again should show the remaining data - let mut peeked2 = builder.peek(); + let mut peeked2 = buf.peek(); assert_eq!(peeked2.len(), 4); - assert_eq!(peeked2.get_u32(), 0x8888_8888); + assert_eq!(peeked2.get_num_ne::(), 0x8888_8888); } #[test] fn peek_multiple_times() { let memory = FixedBlockTestMemory::new(nz!(20)); - let mut builder = BytesBuf::new(); + let mut buf = BytesBuf::new(); - builder.reserve(20, &memory); - builder.put_u64(0xAAAA_AAAA_AAAA_AAAA); + buf.reserve(20, &memory); + buf.put_num_ne(0xAAAA_AAAA_AAAA_AAAA_u64); // Peek multiple times - each should work independently - let mut peeked1 = builder.peek(); - let mut peeked2 = builder.peek(); + let mut peeked1 = buf.peek(); + let mut peeked2 = buf.peek(); - assert_eq!(peeked1.get_u64(), 0xAAAA_AAAA_AAAA_AAAA); - assert_eq!(peeked2.get_u64(), 0xAAAA_AAAA_AAAA_AAAA); + assert_eq!(peeked1.get_num_ne::(), 0xAAAA_AAAA_AAAA_AAAA); + assert_eq!(peeked2.get_num_ne::(), 0xAAAA_AAAA_AAAA_AAAA); // Original builder still intact - assert_eq!(builder.len(), 8); + assert_eq!(buf.len(), 8); + } + + // To be stabilized soon: https://github.com/rust-lang/rust/issues/79995 + fn write_copy_of_slice(dst: &mut [MaybeUninit], src: &[u8]) { + assert!(dst.len() >= src.len()); + + // SAFETY: We have verified that dst is large enough. + unsafe { + src.as_ptr().copy_to_nonoverlapping(dst.as_mut_ptr().cast(), src.len()); + } } } diff --git a/crates/bytesbuf/src/buf_put.rs b/crates/bytesbuf/src/buf_put.rs new file mode 100644 index 00000000..f47ff2a6 --- /dev/null +++ b/crates/bytesbuf/src/buf_put.rs @@ -0,0 +1,343 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +//! We separate out the mutation functions for ease of maintenance. + +use std::borrow::Borrow; +use std::ptr; + +use num_traits::ToBytes; + +use crate::{BytesBuf, BytesView}; + +impl BytesBuf { + /// Appends a slice of bytes to the buffer. + /// + /// # Panics + /// + /// Panics if there is insufficient remaining capacity in the buffer. + pub fn put_slice(&mut self, src: impl Borrow<[u8]>) { + let mut src = src.borrow(); + + assert!(self.remaining_capacity() >= src.len()); + + while !src.is_empty() { + let dst = self.first_unfilled_slice(); + + let to_copy_len = dst.len().min(src.len()); + + // Sanity check - we verified lengths above but let's be defensive. + debug_assert_ne!(to_copy_len, 0); + + let (to_copy, remainder) = src.split_at(to_copy_len); + + // SAFETY: Both are byte slices, so no alignment concerns. + // We guard against length overflow via min() to constrain to slice length. + unsafe { + ptr::copy_nonoverlapping(to_copy.as_ptr(), dst.as_mut_ptr().cast(), to_copy_len); + } + + // SAFETY: Yes, we really did just write `to_copy_len` bytes. + unsafe { + self.advance(to_copy_len); + } + + src = remainder; + } + + // Sanity check to protect against silly mutations. + debug_assert!(self.len() >= src.len()); + } + + /// Appends a byte sequence to the buffer. + /// + /// This reuses the existing capacity of the view being appended. + /// + /// # Panics + /// + /// Panics if there is insufficient remaining capacity in the buffer. + pub fn put_bytes(&mut self, view: BytesView) { + self.append(view); + } + + /// Appends a `u8` to the buffer. + /// + /// # Panics + /// + /// Panics if there is insufficient remaining capacity in the buffer. + pub fn put_byte(&mut self, value: u8) { + self.put_num_ne(value); + } + + /// Appends multiple repetitions of a `u8` to the buffer. + /// + /// # Panics + /// + /// Panics if there is insufficient remaining capacity in the buffer. + pub fn put_byte_repeated(&mut self, value: u8, mut count: usize) { + assert!(self.remaining_capacity() >= count); + + while count > 0 { + let dst = self.first_unfilled_slice(); + let to_fill_len = dst.len().min(count); + + // Sanity check - we verified lengths above but let's be defensive. + debug_assert_ne!(to_fill_len, 0); + + // SAFETY: We are writing bytes, which is always valid, and we have + // guarded against overflow via min() to constrain to slice length. + unsafe { + ptr::write_bytes(dst.as_mut_ptr(), value, to_fill_len); + } + + // SAFETY: Yes, we really did just write `to_fill_len` bytes. + unsafe { + self.advance(to_fill_len); + } + + // Will never overflow because it is guarded by min(). + count = count.wrapping_sub(to_fill_len); + } + + // Sanity check to protect against silly mutations. + debug_assert!(self.len() >= count); + } + + /// Appends a number of type `T` in little-endian representation to the buffer. + /// + /// # Panics + /// + /// Panics if there is insufficient remaining capacity in the buffer. + #[expect(clippy::needless_pass_by_value, reason = "tiny numeric types, fine to always pass by value")] + pub fn put_num_le(&mut self, value: T) { + let bytes = value.to_le_bytes(); + self.put_slice(bytes); + } + + /// Appends a number of type `T` in big-endian representation to the buffer. + /// + /// # Panics + /// + /// Panics if there is insufficient remaining capacity in the buffer. + #[expect(clippy::needless_pass_by_value, reason = "tiny numeric types, fine to always pass by value")] + pub fn put_num_be(&mut self, value: T) { + let bytes = value.to_be_bytes(); + self.put_slice(bytes); + } + + /// Appends a number of type `T` in native-endian representation to the buffer. + /// + /// # Panics + /// + /// Panics if there is insufficient remaining capacity in the buffer. + #[expect(clippy::needless_pass_by_value, reason = "tiny numeric types, fine to always pass by value")] + pub fn put_num_ne(&mut self, value: T) { + let bytes = value.to_ne_bytes(); + self.put_slice(bytes); + } +} + +#[cfg_attr(coverage_nightly, coverage(off))] +#[cfg(test)] +mod tests { + use super::*; + use crate::TransparentTestMemory; + + #[test] + fn put_slice() { + let memory = TransparentTestMemory::new(); + let mut buf = memory.reserve(100); + + let data = [1u8, 2, 3, 4, 5]; + buf.put_slice(data); + + assert_eq!(buf.len(), 5); + assert_eq!(buf.remaining_capacity(), 95); + + let bytes = buf.consume_all(); + + assert_eq!(bytes.len(), 5); + assert_eq!(bytes, &data); + } + + #[test] + fn put_slice_empty() { + let memory = TransparentTestMemory::new(); + let mut buf = memory.reserve(100); + + buf.put_slice([]); + + assert_eq!(buf.len(), 0); + assert_eq!(buf.remaining_capacity(), 100); + } + + #[test] + fn put_view_single_span() { + let memory = TransparentTestMemory::new(); + let mut buf = memory.reserve(100); + + let data = [10_u8, 20, 30, 40, 50]; + let view = BytesView::copied_from_slice(&data, &memory); + + buf.put_bytes(view); + + assert_eq!(buf.len(), 5); + // Appending a view brings along its existing memory capacity, consuming none. + assert_eq!(buf.remaining_capacity(), 100); + + let bytes = buf.consume_all(); + + assert_eq!(bytes.len(), 5); + assert_eq!(bytes, &data); + } + + #[test] + fn put_view_multi_span() { + let memory = TransparentTestMemory::new(); + let mut buf = memory.reserve(100); + + let data_part1 = [10_u8, 20]; + let data_part2 = [30_u8, 40, 50]; + let view_part1 = BytesView::copied_from_slice(&data_part1, &memory); + let view_part2 = BytesView::copied_from_slice(&data_part2, &memory); + let view_combined = BytesView::from_views([view_part1, view_part2]); + + buf.put_bytes(view_combined); + + assert_eq!(buf.len(), 5); + // Appending a view brings along its existing memory capacity, consuming none. + assert_eq!(buf.remaining_capacity(), 100); + + let bytes = buf.consume_all(); + + assert_eq!(bytes.len(), 5); + assert_eq!(bytes, &[10_u8, 20, 30, 40, 50]); + } + + #[test] + fn put_view_empty() { + let memory = TransparentTestMemory::new(); + let mut buf = memory.reserve(100); + + let view = BytesView::new(); + + buf.put_bytes(view); + + assert_eq!(buf.len(), 0); + assert_eq!(buf.remaining_capacity(), 100); + } + + #[test] + fn put_view_peeked_from_self() { + let memory = TransparentTestMemory::new(); + let mut buf = memory.reserve(100); + + let data = [1u8, 2, 3, 4, 5]; + buf.put_slice(data); + + let peeked = buf.peek(); + assert_eq!(peeked.len(), 5); + + buf.put_bytes(peeked); + + assert_eq!(buf.len(), 10); + // The peeked view brings along its existing memory capacity, consuming none. + assert_eq!(buf.remaining_capacity(), 95); + + let bytes = buf.consume_all(); + + assert_eq!(bytes.len(), 10); + assert_eq!(bytes, &[1u8, 2, 3, 4, 5, 1, 2, 3, 4, 5]); + } + + #[test] + fn put_byte() { + let memory = TransparentTestMemory::new(); + let mut buf = memory.reserve(10); + + buf.put_byte(0xAB); + buf.put_byte(0xCD); + + assert_eq!(buf.len(), 2); + assert_eq!(buf.remaining_capacity(), 8); + + let bytes = buf.consume_all(); + + assert_eq!(bytes.len(), 2); + assert_eq!(bytes, &[0xAB, 0xCD]); + } + + #[test] + fn put_bytes() { + let memory = TransparentTestMemory::new(); + let mut buf = memory.reserve(10); + + buf.put_byte_repeated(0xFF, 5); + + assert_eq!(buf.len(), 5); + assert_eq!(buf.remaining_capacity(), 5); + + let bytes = buf.consume_all(); + + assert_eq!(bytes.len(), 5); + assert_eq!(bytes, &[0xFF; 5]); + } + + #[test] + fn put_bytes_into_multi_span() { + let memory = TransparentTestMemory::new(); + let mut buf = BytesBuf::new(); + + // Result: 5 + buf.reserve(5, &memory); + // Result: 5 + 5 + buf.reserve(10, &memory); + + buf.put_byte_repeated(0xAA, 10); + + assert_eq!(buf.len(), 10); + assert_eq!(buf.remaining_capacity(), 0); + + let bytes = buf.consume_all(); + + assert_eq!(bytes.len(), 10); + assert_eq!(bytes, &[0xAA; 10]); + } + + #[test] + fn put_num() { + let memory = TransparentTestMemory::new(); + let mut buf = memory.reserve(16); + + buf.put_num_le(0x1234_5678_u32); + buf.put_num_be(0x9ABC_DEF0_u32); + buf.put_num_ne(0x1122_3344_5566_7788_u64); + + assert_eq!(buf.len(), 16); + assert_eq!(buf.remaining_capacity(), 0); + + let bytes = buf.consume_all(); + + assert_eq!(bytes.len(), 16); + + if cfg!(target_endian = "big") { + assert_eq!( + bytes, + &[ + 0x78, 0x56, 0x34, 0x12, // Little-endian 0x12345678 + 0x9A, 0xBC, 0xDE, 0xF0, // Big-endian 0x9ABCDEF0 + 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88 // Native-endian 0x1122334455667788 + ] + ); + } else { + assert_eq!( + bytes, + &[ + 0x78, 0x56, 0x34, 0x12, // Little-endian 0x12345678 + 0x9A, 0xBC, 0xDE, 0xF0, // Big-endian 0x9ABCDEF0 + 0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11 // Native-endian 0x1122334455667788 + ] + ); + } + } +} diff --git a/crates/bytesbuf/src/bytes_compat/buf.rs b/crates/bytesbuf/src/bytes_compat/buf.rs new file mode 100644 index 00000000..b0d7e5de --- /dev/null +++ b/crates/bytesbuf/src/bytes_compat/buf.rs @@ -0,0 +1,81 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use bytes::BufMut; +use bytes::buf::UninitSlice; + +use crate::BytesBuf; + +// SAFETY: The trait documentation does not define any safety requirements we need to fulfill. +// It is unclear why the trait is marked unsafe in the first place. +unsafe impl BufMut for BytesBuf { + #[cfg_attr(test, mutants::skip)] // Trivial forwarder. + #[inline] + fn remaining_mut(&self) -> usize { + self.remaining_capacity() + } + + #[cfg_attr(test, mutants::skip)] // Trivial forwarder. + #[inline] + unsafe fn advance_mut(&mut self, cnt: usize) { + // SAFETY: Forwarding safety requirements to the caller. + unsafe { + self.advance(cnt); + } + } + + #[cfg_attr(test, mutants::skip)] // Trivial forwarder. + #[inline] + fn chunk_mut(&mut self) -> &mut UninitSlice { + UninitSlice::uninit(self.first_unfilled_slice()) + } +} + +#[cfg_attr(coverage_nightly, coverage(off))] +#[cfg(test)] +mod tests { + use super::*; + use crate::TransparentTestMemory; + + #[test] + fn buf_mut_compat() { + let memory = TransparentTestMemory::new(); + let mut buf = memory.reserve(100); + + assert_eq!(buf.remaining_mut(), 100); + + // 100 + 100 + buf.reserve(200, &memory); + + assert_eq!(buf.remaining_mut(), 200); + + let chunk = buf.chunk_mut(); + assert_eq!(chunk.len(), 100); + + // SAFETY: Lies - we did not write anything. But we will also + // not touch the data - we are only inspecting the bookkeeping. + // Good enough for test code. + unsafe { + buf.advance_mut(50); + } + + let chunk = buf.chunk_mut(); + assert_eq!(chunk.len(), 50); + + // SAFETY: See above. + unsafe { + buf.advance_mut(50); + } + + let chunk = buf.chunk_mut(); + assert_eq!(chunk.len(), 100); + + // SAFETY: See above. + unsafe { + buf.advance_mut(100); + } + + let chunk = buf.chunk_mut(); + assert_eq!(chunk.len(), 0); + } +} diff --git a/crates/bytesbuf/src/bytes.rs b/crates/bytesbuf/src/bytes_compat/from_bytes.rs similarity index 91% rename from crates/bytesbuf/src/bytes.rs rename to crates/bytesbuf/src/bytes_compat/from_bytes.rs index 7f802b32..0dc9544b 100644 --- a/crates/bytesbuf/src/bytes.rs +++ b/crates/bytesbuf/src/bytes_compat/from_bytes.rs @@ -1,28 +1,20 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -//! Compatibility layer for types in the `bytes` crate. -//! -//! We support bidirectional translation between `Bytes` and `BytesView` types. -//! -//! `Bytes` to `BytesView` is always zero-copy. `BytesView` to `Bytes` is opportunistically zero-copy, -//! depending on the memory layout of the `BytesView` instance. -//! -//! Besides this module, some functionality is also present directly in the `bytesbuf` types -//! where the logic requires access to private fields of our types for efficiency. - use std::mem::MaybeUninit; use std::num::NonZero; use std::ptr::NonNull; use std::sync::atomic::{self, AtomicUsize}; -use bytes::{BufMut, Bytes}; +use bytes::Bytes; use smallvec::SmallVec; use crate::{Block, BlockRef, BlockRefDynamic, BlockRefVTable, BlockSize, BytesView, MAX_INLINE_SPANS, Span}; impl From for BytesView { /// Converts a `Bytes` instance into a `BytesView`. + /// + /// This operation is always zero-copy, though does cost a small dynamic allocation. fn from(value: Bytes) -> Self { // A Bytes instance may contain any number of bytes, same as a BytesView. However, each // block of memory inside BytesView is limited to BlockSize::MAX, which is a smaller size. @@ -44,12 +36,12 @@ impl From for BytesView { let mut span_builder = block.into_span_builder(); #[expect(clippy::cast_possible_truncation, reason = "a span can never be larger than BlockSize")] - let len = NonZero::new(span_builder.remaining_mut() as BlockSize).expect("splitting Bytes cannot yield zero-sized chunks"); + let len = NonZero::new(span_builder.remaining_capacity() as BlockSize).expect("splitting Bytes cannot yield zero-sized chunks"); // SAFETY: We know that the data is already initialized; we simply declare this to the // SpanBuilder and get it to emit a completed Span from all its contents. unsafe { - span_builder.advance_mut(len.get() as usize); + span_builder.advance(len.get() as usize); } span_builder.consume(len) @@ -200,7 +192,7 @@ impl Iterator for BytesBlockIterator { #[cfg_attr(coverage_nightly, coverage(off))] #[cfg(test)] mod tests { - use bytes::BytesMut; + use bytes::{BufMut, BytesMut}; use super::*; use crate::TransparentTestMemory; @@ -217,7 +209,7 @@ mod tests { assert_eq!(sequence, b"Hello, world!"); // We expect this to be zero-copy - Bytes to BytesView always is. - assert_eq!(sequence.chunk().as_ptr(), bytes_data_ptr); + assert_eq!(sequence.first_slice().as_ptr(), bytes_data_ptr); } #[test] @@ -226,9 +218,9 @@ mod tests { let sequence = BytesView::copied_from_slice(b"Hello, world!", &memory); - let sequence_chunk_ptr = sequence.chunk().as_ptr(); + let sequence_chunk_ptr = sequence.first_slice().as_ptr(); - let bytes = sequence.into_bytes(); + let bytes = sequence.to_bytes(); assert_eq!(bytes.as_ref(), b"Hello, world!"); @@ -242,9 +234,9 @@ mod tests { let hello = BytesView::copied_from_slice(b"Hello, ", &memory); let world = BytesView::copied_from_slice(b"world!", &memory); - let sequence = BytesView::from_sequences([hello, world]); + let sequence = BytesView::from_views([hello, world]); - let bytes = sequence.into_bytes(); + let bytes = sequence.to_bytes(); assert_eq!(bytes.as_ref(), b"Hello, world!"); } @@ -267,7 +259,7 @@ mod tests { let sequence: BytesView = bytes.into(); assert_eq!(sequence.len(), 5_000_000_000); - assert_eq!(sequence.chunk().len(), u32::MAX as usize); + assert_eq!(sequence.first_slice().len(), u32::MAX as usize); assert_eq!(sequence.into_spans_reversed().len(), 2); } diff --git a/crates/bytesbuf/src/bytes_compat/mod.rs b/crates/bytesbuf/src/bytes_compat/mod.rs new file mode 100644 index 00000000..23a4c056 --- /dev/null +++ b/crates/bytesbuf/src/bytes_compat/mod.rs @@ -0,0 +1,9 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +//! Compatibility with types from the `bytes` packages. + +mod buf; +mod from_bytes; +mod to_bytes; +mod view; diff --git a/crates/bytesbuf/src/bytes_compat/to_bytes.rs b/crates/bytesbuf/src/bytes_compat/to_bytes.rs new file mode 100644 index 00000000..66d1f7b5 --- /dev/null +++ b/crates/bytesbuf/src/bytes_compat/to_bytes.rs @@ -0,0 +1,106 @@ +// 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. + /// + /// We do not expose `From for Bytes` because this is not guaranteed to be a cheap + /// operation and may involve data copying, so `.to_bytes()` must be explicitly called to + /// make the conversion obvious. + /// + /// # 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. + /// + /// This conversion requires a small dynamic memory allocation for + /// metadata, so avoiding conversions is valuable even if zero-copy. + #[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() + } + } +} + +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(test)] +mod tests { + use bytes::Buf; + use new_zealand::nz; + + use super::*; + use crate::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 sequence_single_span = BytesView::from_spans(vec![span1.clone()]); + let sequence_multi_span = BytesView::from_spans(vec![span1, span2]); + + let mut bytes = sequence_single_span.to_bytes(); + assert_eq!(8, bytes.len()); + assert_eq!(1234, bytes.get_u64_ne()); + + let mut bytes = sequence_single_span.to_bytes(); + assert_eq!(8, bytes.len()); + assert_eq!(1234, bytes.get_u64_ne()); + + let mut bytes = sequence_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()); + } +} diff --git a/crates/bytesbuf/src/bytes_compat/view.rs b/crates/bytesbuf/src/bytes_compat/view.rs new file mode 100644 index 00000000..e3a22638 --- /dev/null +++ b/crates/bytesbuf/src/bytes_compat/view.rs @@ -0,0 +1,81 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use std::io::IoSlice; + +use bytes::Buf; + +use crate::BytesView; + +impl Buf for BytesView { + #[cfg_attr(test, mutants::skip)] // Trivial forwarder. + fn remaining(&self) -> usize { + self.len() + } + + #[cfg_attr(test, mutants::skip)] // Trivial forwarder. + fn chunk(&self) -> &[u8] { + self.first_slice() + } + + #[cfg_attr(test, mutants::skip)] // Trivial forwarder. + fn chunks_vectored<'a>(&'a self, dst: &mut [IoSlice<'a>]) -> usize { + self.io_slices(dst) + } + + #[cfg_attr(test, mutants::skip)] // Trivial forwarder. + fn advance(&mut self, cnt: usize) { + self.advance(cnt); + } +} + +#[cfg_attr(coverage_nightly, coverage(off))] +#[cfg(test)] +mod tests { + use new_zealand::nz; + + use super::*; + use crate::FixedBlockTestMemory; + + #[test] + fn buf_compat() { + let memory = FixedBlockTestMemory::new(nz!(25)); + + // 25 x 4 + let mut buf = memory.reserve(100); + buf.put_byte_repeated(0x44, 100); + + let mut bytes = buf.consume_all(); + + assert_eq!(bytes.remaining(), 100); + + let chunk = bytes.chunk(); + assert_eq!(chunk.len(), 25); + assert_eq!(chunk, &[0x44; 25]); + + bytes.advance(20); + + let chunk = bytes.chunk(); + assert_eq!(chunk.len(), 5); + assert_eq!(chunk, &[0x44; 5]); + + bytes.advance(5); + + let chunk = bytes.chunk(); + assert_eq!(chunk.len(), 25); + assert_eq!(chunk, &[0x44; 25]); + + bytes.advance(5); + + let mut io_slices = [IoSlice::new(&[]); 4]; + let n = bytes.chunks_vectored(&mut io_slices); + + // We have already advanced past the first 30 bytes + // but the remaining 70 should still be here for us as 20 + 25 + 25. + assert_eq!(n, 3); + + assert_eq!(&*io_slices[0], &[0x44; 20]); + assert_eq!(&*io_slices[1], &[0x44; 25]); + assert_eq!(&*io_slices[2], &[0x44; 25]); + } +} diff --git a/crates/bytesbuf/src/fixed_block.rs b/crates/bytesbuf/src/fixed_block.rs index 89dacd97..b1e66f7b 100644 --- a/crates/bytesbuf/src/fixed_block.rs +++ b/crates/bytesbuf/src/fixed_block.rs @@ -105,11 +105,11 @@ mod tests { let mut sequence = BytesView::copied_from_slice(b"Hello, world", &memory); assert_eq!(sequence, b"Hello, world"); - assert_eq!(sequence.chunk().len(), 1); + assert_eq!(sequence.first_slice().len(), 1); let mut chunks_encountered: usize = 0; - sequence.consume_all_chunks(|chunk| { + sequence.consume_all_slices(|chunk| { chunks_encountered = chunks_encountered.saturating_add(1); assert_eq!(chunk.len(), 1); }); diff --git a/crates/bytesbuf/src/global.rs b/crates/bytesbuf/src/global.rs index 21aaabcc..782fa02c 100644 --- a/crates/bytesbuf/src/global.rs +++ b/crates/bytesbuf/src/global.rs @@ -307,7 +307,6 @@ mod tests { use std::thread; - use bytes::BufMut; use static_assertions::assert_impl_all; use super::*; @@ -345,32 +344,35 @@ mod tests { fn piece_by_piece() { const SEQUENCE_SIZE_BYTES: BlockSize = 1000; - // We grab a block of memory and split the single block into multiple sequences piece by piece. + // We grab a block of memory and split the single block into multiple views piece by piece. let memory = GlobalPool::new(); - let mut sb = memory.reserve(BLOCK_SIZE_BYTES.get() as usize); + let mut buf = memory.reserve(BLOCK_SIZE_BYTES.get() as usize); - let mut sequences = Vec::new(); + let mut views = Vec::new(); - while sb.has_remaining_mut() { + while buf.remaining_capacity() > 0 { #[expect(clippy::cast_possible_truncation, reason = "intentionally truncating")] - let value = sequences.len() as u8; + let value = views.len() as u8; + + buf.put_byte_repeated(value, (SEQUENCE_SIZE_BYTES as usize).min(buf.remaining_capacity())); - sb.put_bytes(value, (SEQUENCE_SIZE_BYTES as usize).min(sb.remaining_mut())); + // Sanity check against silly mutations. + debug_assert!(!buf.is_empty()); - sequences.push(sb.consume_all()); + views.push(buf.consume_all()); } let expected_count = BLOCK_SIZE_BYTES.get().div_ceil(SEQUENCE_SIZE_BYTES); - assert_eq!(sequences.len(), expected_count as usize); + assert_eq!(views.len(), expected_count as usize); - assert!(!sequences.is_empty()); + assert!(!views.is_empty()); - for (i, sequence) in sequences.iter().enumerate() { + for (i, sequence) in views.iter().enumerate() { #[expect(clippy::cast_possible_truncation, reason = "intentionally truncating")] let expected_value = i as u8; - assert_eq!(sequence.chunk()[0], expected_value); + assert_eq!(sequence.first_slice()[0], expected_value); } } @@ -379,7 +381,7 @@ mod tests { let memory = GlobalPool::new(); let mut sb = memory.reserve(BLOCK_SIZE_BYTES.get() as usize); - sb.put_bytes(42, BLOCK_SIZE_BYTES.get() as usize); + sb.put_byte_repeated(42, BLOCK_SIZE_BYTES.get() as usize); let sequence = sb.consume_all(); @@ -405,7 +407,7 @@ mod tests { let mut sb = memory.reserve(SIZE_10MB); - sb.put(pattern.as_slice()); + sb.put_slice(pattern.as_slice()); let sequence = sb.consume_all(); @@ -429,11 +431,11 @@ mod tests { // Create first sequence with ascending pattern let mut sb1 = memory.reserve(SIZE_10MB); - sb1.put(pattern1.as_slice()); + sb1.put_slice(pattern1.as_slice()); // Create second sequence with descending pattern let mut sb2 = memory.reserve(SIZE_10MB); - sb2.put(pattern2.as_slice()); + sb2.put_slice(pattern2.as_slice()); let sequence1 = sb1.consume_all(); let sequence2 = sb2.consume_all(); diff --git a/crates/bytesbuf/src/lib.rs b/crates/bytesbuf/src/lib.rs index e86691cb..8389c0a4 100644 --- a/crates/bytesbuf/src/lib.rs +++ b/crates/bytesbuf/src/lib.rs @@ -4,40 +4,43 @@ #![cfg_attr(coverage_nightly, feature(coverage_attribute))] #![cfg_attr(docsrs, feature(doc_auto_cfg))] -//! Manipulate sequences of bytes for efficient I/O. +//! Create and manipulate byte sequences for efficient I/O. //! -//! A [`BytesView`] is a view over a logical sequence of zero or more bytes -//! stored in memory, similar to a slice `&[u8]` but with some key differences: +//! A byte sequence is a logical sequence of zero or more bytes stored in memory, +//! similar to a slice `&[u8]` but with some key differences: //! //! * The bytes in a byte sequence are not required to be consecutive in memory. -//! * The bytes in a byte sequence are always immutable, even if you own the [`BytesView`]. +//! * The bytes in a byte sequence are always immutable. //! //! In practical terms, you may think of a byte sequence as a `Vec>` whose contents are -//! treated as one logical sequence of bytes. The types in this crate provide a way to work with -//! byte sequences using an API that is reasonably convenient while also being compatible with -//! the requirements of high-performance zero-copy I/O operations. +//! treated as one logical sequence of bytes. Byte sequences are created via [`BytesBuf`] and +//! consumed via [`BytesView`]. //! //! # Consuming Byte Sequences //! -//! The standard model for using bytes of data from a [`BytesView`] is to consume them via the -//! [`bytes::buf::Buf`][17] trait, which is implemented by [`BytesView`]. +//! A byte sequence is typically consumed by reading its contents. This is done via the +//! [`BytesView`] type, which is a view over a byte sequence. When reading data, the read +//! bytes are removed from the view, shrinking it to only the remaining bytes. //! -//! There are many helper methods on this trait that will read bytes from the beginning of the -//! sequence and simultaneously remove the read bytes from the sequence, shrinking it to only -//! the remaining bytes. +//! There are many helper methods on this type for easily consuming bytes from the view: +//! +//! * [`get_num_le::()`] reads numbers. Big-endian/native-endian variants also exist. +//! * [`get_byte()`] reads a single byte. +//! * [`copy_to_slice()`] copies bytes into a provided slice. +//! * [`copy_to_uninit_slice()`] copies bytes into a provided uninitialized slice. +//! * [`as_read()`] creates a `std::io::Read` adapter for reading bytes via standard I/O methods. //! //! ``` //! # let memory = bytesbuf::GlobalPool::new(); //! # let message = BytesView::copied_from_slice(b"1234123412341234", &memory); -//! use bytes::Buf; //! use bytesbuf::BytesView; //! //! fn consume_message(mut message: BytesView) { //! // We read the message and calculate the sum of all the words in it. //! let mut sum: u64 = 0; //! -//! while message.has_remaining() { -//! let word = message.get_u64(); +//! while !message.is_empty() { +//! let word = message.get_num_le::(); //! sum = sum.saturating_add(word); //! } //! @@ -46,37 +49,35 @@ //! # consume_message(message); //! ``` //! -//! If the helper methods are not sufficient, you can access the contents via byte slices using the -//! more fundamental methods of the [`bytes::buf::Buf`][17] trait such as: +//! If the helper methods are not sufficient, you can access the byte sequence via byte slices using the +//! following fundamental methods that underpin the convenience methods: //! -//! * [`chunk()`][21], which returns a slice of bytes from the beginning of the sequence. The +//! * [`first_slice()`], which returns the first slice of bytes that makes up the byte sequence. The //! length of this slice is determined by the inner structure of the byte sequence and it may not -//! contain all the bytes in the sequence. -//! * [`advance()`][22], which removes bytes from the beginning of the sequence, advancing the -//! head to a new position. When you advance past the slice returned by `chunk()`, the next -//! call to `chunk()` will return a new slice of bytes starting from the new head position. -//! * [`chunks_vectored()`][23], which returns multiple slices of bytes from the beginning of the -//! sequence. This can be desirable for advanced access models that can consume multiple -//! chunks of data at the same time. +//! contain all the bytes. +//! * [`advance()`][ViewAdvance], which marks bytes from the beginning of [`first_slice()`] as read, shrinking the +//! view of the byte sequence by the corresponding amount and moving remaining data up to the front. +//! When you advance past the slice returned by [`first_slice()`], the next call to [`first_slice()`] +//! will return a new slice of bytes starting from the new front position of the view. //! //! ``` //! # let memory = bytesbuf::GlobalPool::new(); -//! # let mut sequence = BytesView::copied_from_slice(b"1234123412341234", &memory); -//! use bytes::Buf; +//! # let mut bytes = BytesView::copied_from_slice(b"1234123412341234", &memory); //! use bytesbuf::BytesView; //! -//! let len = sequence.len(); -//! let mut chunk_lengths = Vec::new(); +//! let len = bytes.len(); +//! let mut slice_lengths = Vec::new(); //! -//! while sequence.has_remaining() { -//! let chunk = sequence.chunk(); -//! chunk_lengths.push(chunk.len()); +//! while !bytes.is_empty() { +//! let slice = bytes.first_slice(); +//! slice_lengths.push(slice.len()); //! -//! // We have completed processing this chunk, all we wanted was to know its length. -//! sequence.advance(chunk.len()); +//! // We have completed processing this slice. All we wanted was to know its length. +//! // We can now mark this slice as consumed, revealing the next slice for inspection. +//! bytes.advance(slice.len()); //! } //! -//! println!("Inspected a sequence of {len} bytes with chunk lengths: {chunk_lengths:?}"); +//! println!("Inspected a view over {len} bytes with slice lengths: {slice_lengths:?}"); //! ``` //! //! To reuse a byte sequence, clone it before consuming the contents. This is a cheap @@ -84,20 +85,20 @@ //! //! ``` //! # let memory = bytesbuf::GlobalPool::new(); -//! # let mut sequence = BytesView::copied_from_slice(b"1234123412341234", &memory); -//! use bytes::Buf; +//! # let mut bytes = BytesView::copied_from_slice(b"1234123412341234", &memory); //! use bytesbuf::BytesView; //! -//! assert_eq!(sequence.len(), 16); +//! assert_eq!(bytes.len(), 16); //! -//! let mut sequence_clone = sequence.clone(); -//! assert_eq!(sequence_clone.len(), 16); +//! let mut bytes_clone = bytes.clone(); +//! assert_eq!(bytes_clone.len(), 16); //! -//! _ = sequence_clone.get_u64(); -//! assert_eq!(sequence_clone.len(), 8); +//! // Consume 8 bytes from the front. +//! _ = bytes_clone.get_num_le::(); +//! assert_eq!(bytes_clone.len(), 8); //! -//! // Operations on the clone have no effect on the original sequence. -//! assert_eq!(sequence.len(), 16); +//! // Operations on the clone have no effect on the original view. +//! assert_eq!(bytes.len(), 16); //! ``` //! //! # Producing Byte Sequences @@ -109,18 +110,18 @@ //! from the following list: //! //! 1. If you are creating byte sequences for the purpose of submitting them to a specific -//! object of a known type (e.g. writing them to a network connection), the target type will +//! object of a known type (e.g. writing them to a `TcpConnection`), the target type will //! typically implement the [`HasMemory`] trait, which gives you a suitable memory -//! provider instance via [`HasMemory::memory`]. Use it - this memory provider will +//! provider instance via [`HasMemory::memory()`]. Use this as the memory provider - it will //! give you memory with the configuration that is optimal for delivering bytes to that -//! specific instance. +//! specific consumer. //! 1. If you are creating byte sequences as part of usage-neutral data processing, obtain an -//! instance of [`GlobalPool`]. In a typical web application framework, this is a service -//! exposed by the application framework. In a different context (e.g. example or test code -//! with no framework), you can create your own instance via `GlobalPool::new()`. +//! instance of a shared [`GlobalPool`]. In a typical web application, the global memory pool +//! is a service exposed by the application framework. In a different context (e.g. example +//! or test code with no framework), you can create your own instance via `GlobalPool::new()`. //! //! Once you have a memory provider, you can reserve memory from it by calling -//! [`Memory::reserve`] on it. This returns a [`BytesBuf`] with the requested +//! [`Memory::reserve()`] on it. This returns a [`BytesBuf`] with the requested //! memory capacity. //! //! ``` @@ -131,149 +132,147 @@ //! //! let memory = connection.memory(); //! -//! let mut sequence_builder = memory.reserve(100); +//! let mut buf = memory.reserve(100); //! ``` //! -//! Now that you have the memory capacity and a [`BytesBuf`], you can fill the memory -//! capacity with bytes of data. The standard pattern for this is to use the -//! [`bytes::buf::BufMut`][20] trait, which is implemented by [`BytesBuf`]. +//! Now that you have the memory capacity in a [`BytesBuf`], you can fill the memory +//! capacity with bytes of data. Creating byte sequences in a [`BytesBuf`] is an +//! append-only process - you can only add data to the end of the buffered sequence. +//! +//! There are many helper methods on [`BytesBuf`] for easily appending bytes to the buffer: //! -//! Helper methods on this trait allow you to write bytes to the sequence builder up to the -//! extent of the reserved memory capacity. +//! * [`put_num_le::()`], which appends numbers. Big-endian/native-endian variants also exist. +//! * [`put_slice()`], which appends a slice of bytes. +//! * [`put_byte()`], which appends a single byte. +//! * [`put_byte_repeated()`], which appends multiple repetitions of a byte. +//! * [`put_bytes()`], which appends an existing [`BytesView`]. //! //! ``` //! # struct Connection {} //! # impl Connection { fn memory(&self) -> impl Memory { bytesbuf::GlobalPool::new() } } //! # let connection = Connection {}; -//! use bytes::buf::BufMut; //! use bytesbuf::Memory; //! //! let memory = connection.memory(); //! -//! let mut sequence_builder = memory.reserve(100); +//! let mut buf = memory.reserve(100); //! -//! sequence_builder.put_u64(1234); -//! sequence_builder.put_u64(5678); -//! sequence_builder.put(b"Hello, world!".as_slice()); +//! buf.put_num_be(1234_u64); +//! buf.put_num_be(5678_u64); +//! buf.put_slice(*b"Hello, world!"); //! ``` //! -//! If the helper methods are not sufficient, you can append contents via mutable byte slices -//! using the more fundamental methods of the [`bytes::buf::BufMut`][20] trait such as: +//! If the helper methods are not sufficient, you can write contents directly into mutable byte slices +//! using the fundamental methods that underpin the convenience methods: //! -//! * [`chunk_mut()`][24], which returns a mutable slice of bytes from the beginning of the -//! sequence builder's unused capacity. The length of this slice is determined by the inner -//! structure of the sequence builder and it may not contain all the capacity that has been -//! reserved. -//! * [`advance_mut()`][22], which declares that a number of bytes from the beginning of the -//! unused capacity have been initialized with data and are no longer unused. This will -//! mark these bytes as valid for reading and advance `chunk_mut()` to the next slice if the -//! current one has been completely filled. +//! * [`first_unfilled_slice()`], which returns a mutable slice of bytes from the beginning of the +//! buffer's remaining capacity. The length of this slice is determined by the inner memory layout +//! of the buffer and it may not contain all the capacity that has been reserved. +//! * [`advance()`][BufAdvance], which declares that a number of bytes at the beginning of [`first_unfilled_slice()`] +//! have been initialized with data and are no longer unused. This will mark these bytes as valid for +//! consumption and advance [`first_unfilled_slice()`] to the next slice of unused memory capacity +//! if the current slice has been completely filled. //! -//! See `examples/mem_chunk_write.rs` for an example of how to use these methods. +//! See `examples/bb_slice_by_slice_write.rs` for an example of how to use these methods. //! //! If you do not know exactly how much memory you need in advance, you can extend the sequence -//! builder capacity on demand if you run out by calling [`BytesBuf::reserve`], -//! which will reserve more memory capacity. You can use [`bytes::buf::BufMut::remaining_mut()`][26] -//! on the sequence builder to identify how much unused memory capacity is available for writing. +//! builder capacity on demand by calling [`BytesBuf::reserve()`]. You can use [`remaining_capacity()`] +//! to identify how much unused memory capacity is available. //! //! ``` //! # struct Connection {} //! # impl Connection { fn memory(&self) -> impl Memory { bytesbuf::GlobalPool::new() } } //! # let connection = Connection {}; -//! use bytes::buf::BufMut; //! use bytesbuf::Memory; //! //! let memory = connection.memory(); //! -//! let mut sequence_builder = memory.reserve(100); +//! let mut buf = memory.reserve(100); //! -//! // .. write some data into the sequence builder .. +//! // .. write some data into the buffer .. //! //! // We discover that we need 80 additional bytes of memory! No problem. -//! sequence_builder.reserve(80, &memory); +//! buf.reserve(80, &memory); //! //! // Remember that a memory provider can always provide more memory than requested. -//! assert!(sequence_builder.capacity() >= 100 + 80); -//! assert!(sequence_builder.remaining_mut() >= 80); +//! assert!(buf.capacity() >= 100 + 80); +//! assert!(buf.remaining_capacity() >= 80); //! ``` //! -//! When you have filled the memory capacity with the bytes you wanted to write, you can consume -//! the data in the sequence builder, turning it into a [`BytesView`] of immutable bytes. +//! When you have filled the memory capacity with the contents of the byte sequence, you can consume +//! the data in the buffer as a [`BytesView`] over immutable bytes. //! //! ``` //! # struct Connection {} //! # impl Connection { fn memory(&self) -> impl Memory { bytesbuf::GlobalPool::new() } } //! # let connection = Connection {}; -//! use bytes::buf::BufMut; //! use bytesbuf::Memory; //! //! let memory = connection.memory(); //! -//! let mut sequence_builder = memory.reserve(100); +//! let mut buf = memory.reserve(100); //! -//! sequence_builder.put_u64(1234); -//! sequence_builder.put_u64(5678); -//! sequence_builder.put(b"Hello, world!".as_slice()); +//! buf.put_num_be(1234_u64); +//! buf.put_num_be(5678_u64); +//! buf.put_slice(*b"Hello, world!"); //! -//! let message = sequence_builder.consume_all(); +//! let message = buf.consume_all(); //! ``` //! -//! This can be done piece by piece, and you can continue writing to the sequence builder +//! This can be done piece by piece, and you can continue writing to the buffer //! after consuming some already written bytes. //! //! ``` //! # struct Connection {} //! # impl Connection { fn memory(&self) -> impl Memory { bytesbuf::GlobalPool::new() } } //! # let connection = Connection {}; -//! use bytes::buf::BufMut; //! use bytesbuf::Memory; //! //! let memory = connection.memory(); //! -//! let mut sequence_builder = memory.reserve(100); +//! let mut buf = memory.reserve(100); //! -//! sequence_builder.put_u64(1234); -//! sequence_builder.put_u64(5678); +//! buf.put_num_be(1234_u64); +//! buf.put_num_be(5678_u64); //! -//! let first_8_bytes = sequence_builder.consume(8); -//! let second_8_bytes = sequence_builder.consume(8); +//! let first_8_bytes = buf.consume(8); +//! let second_8_bytes = buf.consume(8); //! -//! sequence_builder.put(b"Hello, world!".as_slice()); +//! buf.put_slice(*b"Hello, world!"); //! -//! let final_contents = sequence_builder.consume_all(); +//! let final_contents = buf.consume_all(); //! ``` //! //! If you already have a [`BytesView`] that you want to write into a [`BytesBuf`], call -//! [`BytesBuf::append()`][26]. This is a highly efficient zero-copy operation -//! that reuses the memory capacity of the sequence you are appending. +//! [`BytesBuf::put_bytes()`]. This is a highly efficient zero-copy operation +//! that reuses the memory capacity of the view you are appending. //! //! ``` //! # struct Connection {} //! # impl Connection { fn memory(&self) -> impl Memory { bytesbuf::GlobalPool::new() } } //! # let connection = Connection {}; -//! use bytes::buf::BufMut; //! use bytesbuf::Memory; //! //! let memory = connection.memory(); //! //! let mut header_builder = memory.reserve(16); -//! header_builder.put_u64(1234); +//! header_builder.put_num_be(1234_u64); //! let header = header_builder.consume_all(); //! -//! let mut sequence_builder = memory.reserve(128); -//! sequence_builder.append(header); -//! sequence_builder.put(b"Hello, world!".as_slice()); +//! let mut buf = memory.reserve(128); +//! buf.put_bytes(header); +//! buf.put_slice(*b"Hello, world!"); //! ``` //! -//! Note that there is no requirement that the memory capacity of the sequence builder and the -//! memory capacity of the sequence being appended come from the same memory provider. It is valid +//! Note that there is no requirement that the memory capacity of the buffer and the +//! memory capacity of the view being appended come from the same memory provider. It is valid //! to mix and match memory from different providers, though this may disable some optimizations. //! //! # Implementing APIs that Consume Byte Sequences //! //! If you are implementing a type that accepts byte sequences, you should implement the //! [`HasMemory`] trait to make it possible for the caller to use optimally -//! configured memory. +//! configured memory when creating the byte sequences for input to your type. //! //! Even if the implementation of your type today is not capable of taking advantage of //! optimizations that depend on the memory configuration, it may be capable of doing so @@ -293,14 +292,14 @@ //! nor can take advantage of optimizations enabled by specific memory configurations, obtain //! an instance of [`GlobalPool`] as a dependency and return it as the memory provider. //! -//! Example of forwarding the memory provider (see `examples/mem_has_provider_forwarding.rs` +//! Example of forwarding the memory provider (see `examples/bb_has_memory_forwarding.rs` //! for full code): //! //! ``` //! use bytesbuf::{HasMemory, MemoryShared, BytesView}; //! -//! /// Counts the number of 0x00 bytes in a sequence before -//! /// writing that sequence to a network connection. +//! /// Counts the number of 0x00 bytes in a byte sequence before +//! /// writing that byte sequence to a network connection. //! /// //! /// # Implementation strategy for `HasMemory` //! /// @@ -322,10 +321,10 @@ //! } //! } //! -//! pub fn write(&mut self, sequence: BytesView) { +//! pub fn write(&mut self, message: BytesView) { //! // TODO: Count zeros. //! -//! self.connection.write(sequence); +//! self.connection.write(message); //! } //! } //! @@ -342,7 +341,7 @@ //! ``` //! //! Example of returning a memory provider that performs configuration for optimal memory (see -//! `examples/mem_has_provider_optimizing.rs` for full code): +//! `examples/bb_has_memory_optimizing.rs` for full code): //! //! ``` //! use bytesbuf::{CallbackMemory, HasMemory, MemoryShared, BytesView}; @@ -353,7 +352,7 @@ //! /// the memory is reserved from the I/O memory pool. It uses the I/O context to reserve memory, //! /// providing a usage-specific configuration when reserving memory capacity. //! /// -//! /// A delegating memory provider is used to attach the configuration to each memory reservation. +//! /// A callback memory provider is used to attach the configuration to each memory reservation. //! #[derive(Debug)] //! struct UdpConnection { //! io_context: IoContext, @@ -400,8 +399,8 @@ //! # struct MemoryConfiguration { requires_page_alignment: bool, zero_memory_on_release: bool, requires_registered_memory: bool } //! ``` //! -//! Example of returning a usage-neutral memory provider (see `examples/mem_has_provider_neutral.rs` for -//! full code): +//! Example of returning a global memory pool when the type is agnostic toward memory configuration +//! (see `examples/bb_has_memory_global.rs` for full code): //! //! ``` //! use bytesbuf::{GlobalPool, HasMemory, MemoryShared}; @@ -418,19 +417,19 @@ //! #[derive(Debug)] //! struct ChecksumCalculator { //! // The application logic must provide this - it is our dependency. -//! memory_provider: GlobalPool, +//! memory: GlobalPool, //! } //! //! impl ChecksumCalculator { -//! pub fn new(memory_provider: GlobalPool) -> Self { -//! Self { memory_provider } +//! pub fn new(memory: GlobalPool) -> Self { +//! Self { memory } //! } //! } //! //! impl HasMemory for ChecksumCalculator { //! fn memory(&self) -> impl MemoryShared { -//! // Cloning a memory provider is a cheap operation, as clones reuse resources. -//! self.memory_provider.clone() +//! // Cloning a memory provider is intended to be a cheap operation, reusing resources. +//! self.memory.clone() //! } //! } //! ``` @@ -448,7 +447,7 @@ //! byte sequence. //! //! Example of identifying whether a byte sequence uses the optimal memory configuration (see -//! `examples/mem_optimal_path.rs` for full code): +//! `examples/bb_optimal_path.rs` for full code): //! //! ``` //! # struct Foo; @@ -460,7 +459,7 @@ //! // ues the optimal I/O path. There is no requirement that the data passed to us contains //! // only memory with our preferred configuration. //! -//! let use_optimal_path = message.iter_chunk_metas().all(|meta| { +//! let use_optimal_path = message.iter_slice_metas().all(|meta| { //! // If there is no metadata, the memory is not I/O memory. //! meta.is_some_and(|meta| { //! // If the type of metadata does not match the metadata @@ -494,14 +493,14 @@ //! //! # Compatibility with the `bytes` Crate //! -//! The popular [`Bytes`][18] type from the `bytes` crate is often used in the Rust ecosystem to +//! The popular [`Bytes`] type from the `bytes` crate is often used in the Rust ecosystem to //! represent simple byte buffers of consecutive bytes. For compatibility with this commonly used -//! type, this crate offers conversion methods to translate between [`BytesView`] and [`Bytes`][18]: +//! type, this crate offers conversion methods to translate between [`BytesView`] and [`Bytes`]: //! -//! * [`BytesView::into_bytes`] converts a [`BytesView`] into a [`Bytes`][18] instance. This +//! * [`BytesView::to_bytes()`] converts a [`BytesView`] into a [`Bytes`] instance. This //! is not always zero-copy because a byte sequence is not guaranteed to be consecutive in memory. //! You are discouraged from using this method in any performance-relevant logic path. -//! * `BytesView::from(Bytes)` or `let s: BytesView = bytes.into()` converts a [`Bytes`][18] instance +//! * `BytesView::from(Bytes)` or `let s: BytesView = bytes.into()` converts a [`Bytes`] instance //! into a [`BytesView`]. This is an efficient zero-copy operation that reuses the memory of the //! `Bytes` instance. //! @@ -519,7 +518,7 @@ //! * We want to use memory that is optimally configured for the context in which the data is //! consumed (e.g. network connection, file, etc). //! -//! The standard pattern here is to use [`OnceLock`][27] to lazily initialize a [`BytesView`] from +//! The standard pattern here is to use [`OnceLock`] to lazily initialize a [`BytesView`] from //! the static data on first use, using memory from a memory provider that is optimal for the //! intended usage. //! @@ -543,8 +542,9 @@ //! for _ in 0..10 { //! let mut connection = Connection::accept(); //! -//! // The static data is transformed into a BytesView on first use, -//! // using memory optimally configured for a network connection. +//! // The static data is transformed into a BytesView on first use, using memory optimally configured +//! // for network connections. The underlying principle is that memory optimally configured for one network +//! // connection is likely also optimally configured for another network connection, enabling efficient reuse. //! let header_prefix = header_prefix //! .get_or_init(|| BytesView::copied_from_slice(HEADER_PREFIX, &connection.memory())); //! @@ -577,15 +577,28 @@ //! you want to ensure that your code works well even if a byte sequence consists of //! non-consecutive memory. You can go down to as low as 1 byte per block! //! -//! [17]: https://docs.rs/bytes/latest/bytes/buf/trait.Buf.html -//! [18]: https://docs.rs/bytes/latest/bytes/struct.Bytes.html -//! [20]: https://docs.rs/bytes/latest/bytes/buf/trait.BufMut.html -//! [21]: https://docs.rs/bytes/latest/bytes/buf/trait.Buf.html#method.chunk -//! [22]: https://docs.rs/bytes/latest/bytes/buf/trait.Buf.html#method.advance -//! [23]: https://docs.rs/bytes/latest/bytes/buf/trait.Buf.html#method.chunks_vectored -//! [24]: https://docs.rs/bytes/latest/bytes/buf/trait.BufMut.html#method.chunk_mut -//! [26]: https://docs.rs/bytes/latest/bytes/buf/trait.BufMut.html#method.remaining_mut -//! [27]: std::sync::OnceLock +//! [`get_num_le::()`]: crate::BytesView::get_num_le +//! [`get_byte()`]: crate::BytesView::get_byte +//! [`copy_to_slice()`]: crate::BytesView::copy_to_slice +//! [`copy_to_uninit_slice()`]: crate::BytesView::copy_to_uninit_slice +//! [`as_read()`]: crate::BytesView::as_read +//! [`first_slice()`]: crate::BytesView::first_slice +//! [ViewAdvance]: crate::BytesView::advance +//! [`put_num_le::()`]: crate::BytesBuf::put_num_le +//! [`put_slice()`]: crate::BytesBuf::put_slice +//! [`put_byte()`]: crate::BytesBuf::put_byte +//! [`put_byte_repeated()`]: crate::BytesBuf::put_byte_repeated +//! [`put_bytes()`]: crate::BytesBuf::put_bytes +//! [`first_unfilled_slice()`]: crate::BytesBuf::first_unfilled_slice +//! [BufAdvance]: crate::BytesBuf::advance +//! [`BytesView::to_bytes()`]: crate::BytesView::to_bytes +//! [`Memory`]: crate::Memory +//! [`HasMemory`]: crate::HasMemory +//! [`HasMemory::memory()`]: crate::HasMemory::memory +//! [`GlobalPool`]: crate::GlobalPool +//! [`Bytes`]: https://docs.rs/bytes/latest/bytes/struct.Bytes.html +//! [`remaining_capacity()`]: crate::BytesBuf::remaining_capacity +//! [`OnceLock`]: std::sync::OnceLock #![doc(html_logo_url = "https://media.githubusercontent.com/media/microsoft/oxidizer/refs/heads/main/crates/bytesbuf/logo.png")] #![doc(html_favicon_url = "https://media.githubusercontent.com/media/microsoft/oxidizer/refs/heads/main/crates/bytesbuf/favicon.ico")] @@ -593,7 +606,8 @@ mod block; mod block_ref; mod buf; -mod bytes; +mod buf_put; +mod bytes_compat; mod callback_memory; mod constants; mod fixed_block; @@ -603,12 +617,14 @@ mod memory; mod memory_guard; mod memory_shared; mod opaque_memory; +mod read_adapter; mod slice; mod span; mod span_builder; mod transparent; mod vec; mod view; +mod view_get; mod write_adapter; pub use block::{Block, BlockSize}; @@ -626,7 +642,7 @@ pub use opaque_memory::OpaqueMemory; pub(crate) use span::Span; pub(crate) use span_builder::SpanBuilder; pub use transparent::TransparentTestMemory; -pub use view::{BytesView, BytesViewChunkMetasIterator}; +pub use view::{BytesView, BytesViewSliceMetasIterator}; pub(crate) use write_adapter::BytesBufWrite; #[cfg(test)] diff --git a/crates/bytesbuf/src/read_adapter.rs b/crates/bytesbuf/src/read_adapter.rs new file mode 100644 index 00000000..c9d63f45 --- /dev/null +++ b/crates/bytesbuf/src/read_adapter.rs @@ -0,0 +1,69 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use std::io::{self, Read}; + +use crate::BytesView; + +/// Adapter that implements `std::io::Read` for [`BytesView`]. +/// +/// Create an instance via [`BytesView::as_read()`][1]. +/// +/// [1]: crate::BytesView::as_read +#[derive(Debug)] +pub(crate) struct BytesViewReader<'b> { + inner: &'b mut BytesView, +} + +impl<'b> BytesViewReader<'b> { + #[must_use] + pub(crate) const fn new(inner: &'b mut BytesView) -> Self { + Self { inner } + } +} + +impl Read for BytesViewReader<'_> { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + if self.inner.is_empty() { + return Ok(0); + } + + let to_read = buf.len().min(self.inner.len()); + self.inner.copy_to_slice(&mut buf[..to_read]); + Ok(to_read) + } +} + +#[cfg_attr(coverage_nightly, coverage(off))] +#[cfg(test)] +mod tests { + use super::*; + use crate::TransparentTestMemory; + + #[test] + fn smoke_test() { + let memory = TransparentTestMemory::new(); + let mut view = BytesView::copied_from_slice(b"Hello, world", &memory); + let mut reader = view.as_read(); + + let mut buffer = [0u8; 5]; + let bytes_read = reader.read(&mut buffer).unwrap(); + + // We use white-box knowledge to know that we always read as much as is available, + // there are no partial reads unless at end of data. This simplifies the test logic. + assert_eq!(bytes_read, 5); + assert_eq!(&buffer, b"Hello"); + + let bytes_read = reader.read(&mut buffer).unwrap(); + + assert_eq!(bytes_read, 5); + assert_eq!(&buffer, b", wor"); + + let bytes_read = reader.read(&mut buffer).unwrap(); + assert_eq!(bytes_read, 2); + assert_eq!(&buffer[..2], b"ld"); + + let bytes_read = reader.read(&mut buffer).unwrap(); + assert_eq!(bytes_read, 0); + } +} diff --git a/crates/bytesbuf/src/span.rs b/crates/bytesbuf/src/span.rs index a3570f74..8bee23ad 100644 --- a/crates/bytesbuf/src/span.rs +++ b/crates/bytesbuf/src/span.rs @@ -5,24 +5,29 @@ use std::ops::{Bound, Deref, RangeBounds}; use std::ptr::NonNull; use std::{fmt, slice}; -use bytes::{Buf, Bytes}; - use crate::{BlockRef, BlockSize}; -/// A span of immutable bytes backed by memory from a memory block. This type is used as a building -/// block for [`BytesView`][crate::BytesView]s. +/// A span of immutable bytes backed by memory from a memory block. +/// +/// This type is used as a building block for [`BytesView`][crate::BytesView]. /// /// While the contents are immutable, the span itself is not - its size may be constrained by -/// cutting off pieces from the front (consuming them) as the read cursor is advanced when calling -/// members of the [`bytes::Buf][1]` trait implementation. +/// cutting off bytes from the front (i.e. consuming them). /// /// Contents that have been consumed are no longer considered part of the span (any remaining -/// content shifts to index 0 after data is consumed from the front). +/// content shifts to index 0). /// /// Sub-slices of a span may be formed by calling [`.slice()`]. This does not copy the data, -/// merely creates a new and independent view over the same immutable memory. +/// merely creates a new and independent view over the same immutable bytes. +/// +/// # Ownership of memory blocks +/// +/// See [`SpanBuilder`][crate::SpanBuilder] for details. #[derive(Clone)] pub(crate) struct Span { + // For the purposes of the `Span` and `SpanBuilder` types, this merely controls the lifecycle + // of the memory block - dropping the last reference will permit the memory block to be + // reclaimed by the memory provider it originates from. block_ref: BlockRef, start: NonNull, @@ -99,15 +104,48 @@ impl Span { }) } - /// Allows the underlying memory block to be accessed, primarily used to extend its lifetime - /// beyond that of the `Span` itself. + /// References the memory block that provides the span's memory capacity. pub(crate) const fn block_ref(&self) -> &BlockRef { &self.block_ref } - /// Returns the span as an instance of `Bytes`. This operation is zero-copy. - pub(crate) fn to_bytes(&self) -> Bytes { - Bytes::from_owner(self.clone()) + /// Marks `len` bytes as consumed from the start of the span, shrinking it. + /// + /// The remaining bytes shift to index 0. + /// + /// # Safety + /// + /// The caller must ensure that `len` is less than or equal to the current length of the span. + pub(crate) unsafe fn advance(&mut self, len: usize) { + #[expect(clippy::cast_possible_truncation, reason = "guaranteed by safety requirements")] + let len_bs = len as BlockSize; + + // Will never wrap - guaranteed by safety requirements. + self.len = self.len.wrapping_sub(len_bs); + + // SAFETY: Guaranteed by safety requirements. + self.start = unsafe { self.start.add(len) }; + } + + /// Testing helper for easily consuming a fixed number of bytes from the front. + #[cfg(test)] + #[expect(clippy::cast_possible_truncation, reason = "test code, relax")] + pub(crate) fn get_array(&mut self) -> [u8; N] { + assert!(self.len() >= N as BlockSize, "out of bounds read"); + + let mut array = [0_u8; N]; + + // SAFETY: Assertion above guarantees that we have enough bytes of data in the span. + let src = unsafe { slice::from_raw_parts(self.start.as_ptr(), N) }; + + array.copy_from_slice(src); + + // SAFETY: Guarded by assertion above. + unsafe { + self.advance(N); + } + + array } } @@ -129,41 +167,18 @@ impl AsRef<[u8]> for Span { } } -impl Buf for Span { - #[cfg_attr(test, mutants::skip)] // Mutating this can cause infinite loops. - fn remaining(&self) -> usize { - self.len as usize - } - - #[cfg_attr(test, mutants::skip)] // Mutating this can cause infinite loops. - fn chunk(&self) -> &[u8] { - self - } - - fn advance(&mut self, cnt: usize) { - // If it does not fit into BlockSize, it for sure does not fit in the block. - let count: BlockSize = BlockSize::try_from(cnt).expect("attempted to advance past end of span"); - - // Length is subtracted first, so even if we panic later, we do not overshoot the block. - self.len = self.len.checked_sub(count).expect("attempted to advance past end of span"); - - // SAFETY: We validated above that the pointer remains in-bounds. - self.start = unsafe { self.start.add(count as usize) }; - } -} - impl fmt::Debug for Span { + #[cfg_attr(coverage_nightly, coverage(off))] // There is no specific API contract here for us to test. fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut debug_struct = f.debug_struct("Span"); debug_struct.field("start", &self.start).field("len", &self.len); if self.len >= 4 { - let mut clone = self.clone(); - let byte1 = clone.get_u8(); - let byte2 = clone.get_u8(); - let byte3 = clone.get_u8(); - let byte4 = clone.get_u8(); + let byte1 = self[0]; + let byte2 = self[1]; + let byte3 = self[2]; + let byte4 = self[3]; debug_struct.field("first_four_bytes", &format!("{byte1:02x}{byte2:02x}{byte3:02x}{byte4:02x}")); } @@ -179,60 +194,48 @@ unsafe impl Send for Span {} // state is thread-safe. Furthermore, instances are immutable so sharing is natural. unsafe impl Sync for Span {} +#[cfg_attr(coverage_nightly, coverage(off))] #[cfg(test)] mod tests { use std::num::NonZero; - use bytes::BufMut; use new_zealand::nz; use static_assertions::assert_impl_all; - use testing_aids::assert_panic; use super::*; use crate::std_alloc_block; + // The type is thread-mobile (Send) and can be shared (for reads) between threads (Sync). + assert_impl_all!(Span: Send, Sync); + #[test] fn smoke_test() { let mut builder = std_alloc_block::allocate(nz!(10)).into_span_builder(); - builder.put_u64(1234); - builder.put_u16(16); + builder.put_slice(&1234_u64.to_ne_bytes()); + builder.put_slice(&16_u16.to_ne_bytes()); let mut span = builder.consume(nz!(10)); - assert_eq!(0, builder.remaining_mut()); - assert_eq!(span.remaining(), 10); + assert_eq!(0, builder.remaining_capacity()); + assert_eq!(span.len(), 10); assert!(!span.is_empty()); - let slice = span.chunk(); - assert_eq!(10, slice.len()); + assert_eq!(10, span.as_ref().len()); - assert_eq!(span.get_u64(), 1234); - assert_eq!(span.get_u16(), 16); + assert_eq!(u64::from_ne_bytes(span.get_array()), 1234); + assert_eq!(u16::from_ne_bytes(span.get_array()), 16); - assert_eq!(0, span.remaining()); + assert_eq!(0, span.len()); assert!(span.is_empty()); } - #[test] - fn oob_is_panic() { - let mut builder = std_alloc_block::allocate(nz!(10)).into_span_builder(); - - builder.put_u64(1234); - builder.put_u16(16); - - let mut span = builder.consume(nz!(10)); - - assert_eq!(span.get_u64(), 1234); - assert_panic!(_ = span.get_u32()); // Reads 4 but only has 2 remaining. - } - #[test] fn slice() { let mut builder = std_alloc_block::allocate(nz!(10)).into_span_builder(); - builder.put_u64(1234); - builder.put_u16(16); + builder.put_slice(&1234_u64.to_ne_bytes()); + builder.put_slice(&16_u16.to_ne_bytes()); let mut span1 = builder.consume(nz!(10)); let mut span2 = span1.slice(0..10); @@ -240,24 +243,17 @@ mod tests { let span4 = span1.slice(10..10); assert!(span1.slice_checked(0..11).is_none()); // Out of bounds. - assert_eq!(span1.remaining(), 10); - assert_eq!(span2.remaining(), 10); - assert_eq!(span3.remaining(), 2); - assert_eq!(span4.remaining(), 0); - - assert_eq!(span1.get_u64(), 1234); - assert_eq!(span1.get_u16(), 16); + assert_eq!(span1.len(), 10); + assert_eq!(span2.len(), 10); + assert_eq!(span3.len(), 2); + assert_eq!(span4.len(), 0); - assert_eq!(span2.get_u64(), 1234); - assert_eq!(span2.get_u16(), 16); + assert_eq!(u64::from_ne_bytes(span1.get_array()), 1234); + assert_eq!(u16::from_ne_bytes(span1.get_array()), 16); - assert_eq!(span3.get_u16(), 16); - } - - #[test] - fn thread_safe_type() { - // The type is thread-mobile (Send) and can be shared (for reads) between threads (Sync). - assert_impl_all!(Span: Send, Sync); + assert_eq!(u64::from_ne_bytes(span2.get_array()), 1234); + assert_eq!(u16::from_ne_bytes(span2.get_array()), 16); + assert_eq!(u16::from_ne_bytes(span3.get_array()), 16); } #[test] @@ -272,43 +268,38 @@ mod tests { fn slice_indexing_kinds() { let mut sb = std_alloc_block::allocate(nz!(10)).into_span_builder(); - sb.put_u8(0); - sb.put_u8(1); - sb.put_u8(2); - sb.put_u8(3); - sb.put_u8(4); - sb.put_u8(5); + sb.put_slice(&[0, 1, 2, 3, 4, 5]); let span = sb.consume(NonZero::new(sb.len()).unwrap()); let mut middle_four = span.slice(1..5); assert_eq!(4, middle_four.len()); - assert_eq!(1, middle_four.get_u8()); - assert_eq!(2, middle_four.get_u8()); - assert_eq!(3, middle_four.get_u8()); - assert_eq!(4, middle_four.get_u8()); + assert_eq!(1, u8::from_ne_bytes(middle_four.get_array())); + assert_eq!(2, u8::from_ne_bytes(middle_four.get_array())); + assert_eq!(3, u8::from_ne_bytes(middle_four.get_array())); + assert_eq!(4, u8::from_ne_bytes(middle_four.get_array())); let mut middle_four = span.slice(1..=4); assert_eq!(4, middle_four.len()); - assert_eq!(1, middle_four.get_u8()); - assert_eq!(2, middle_four.get_u8()); - assert_eq!(3, middle_four.get_u8()); - assert_eq!(4, middle_four.get_u8()); + assert_eq!(1, u8::from_ne_bytes(middle_four.get_array())); + assert_eq!(2, u8::from_ne_bytes(middle_four.get_array())); + assert_eq!(3, u8::from_ne_bytes(middle_four.get_array())); + assert_eq!(4, u8::from_ne_bytes(middle_four.get_array())); let mut last_two = span.slice(4..); assert_eq!(2, last_two.len()); - assert_eq!(4, last_two.get_u8()); - assert_eq!(5, last_two.get_u8()); + assert_eq!(4, u8::from_ne_bytes(last_two.get_array())); + assert_eq!(5, u8::from_ne_bytes(last_two.get_array())); let mut first_two = span.slice(..2); assert_eq!(2, first_two.len()); - assert_eq!(0, first_two.get_u8()); - assert_eq!(1, first_two.get_u8()); + assert_eq!(0, u8::from_ne_bytes(first_two.get_array())); + assert_eq!(1, u8::from_ne_bytes(first_two.get_array())); let mut first_two = span.slice(..=1); assert_eq!(2, first_two.len()); - assert_eq!(0, first_two.get_u8()); - assert_eq!(1, first_two.get_u8()); + assert_eq!(0, u8::from_ne_bytes(first_two.get_array())); + assert_eq!(1, u8::from_ne_bytes(first_two.get_array())); } #[test] @@ -317,15 +308,7 @@ mod tests { let mut sb = std_alloc_block::allocate(nz!(100)).into_span_builder(); - sb.put_u8(0); - sb.put_u8(1); - sb.put_u8(2); - sb.put_u8(3); - sb.put_u8(4); - sb.put_u8(5); - sb.put_u8(6); - sb.put_u8(7); - sb.put_u8(8); + sb.put_slice(&[0, 1, 2, 3, 4, 5, 6, 7, 8]); let span = sb.consume(NonZero::new(sb.len()).unwrap()); @@ -335,43 +318,13 @@ mod tests { assert!(sliced.is_some()); let mut sliced = sliced.unwrap(); assert_eq!(3, sliced.len()); - assert_eq!(2, sliced.get_u8()); - assert_eq!(3, sliced.get_u8()); - assert_eq!(4, sliced.get_u8()); + assert_eq!(2, u8::from_ne_bytes(sliced.get_array())); + assert_eq!(3, u8::from_ne_bytes(sliced.get_array())); + assert_eq!(4, u8::from_ne_bytes(sliced.get_array())); // Test edge case: excluded start at the last valid index returns empty sequence let sliced = span.slice_checked((Bound::Excluded(8), Bound::Unbounded)); assert!(sliced.is_some()); assert_eq!(0, sliced.unwrap().len()); } - - #[test] - fn debug_includes_first_four_bytes_for_long_spans() { - let mut builder = std_alloc_block::allocate(nz!(8)).into_span_builder(); - - for byte in [0x10_u8, 0x20, 0x30, 0x40, 0x50] { - builder.put_u8(byte); - } - - let span = builder.consume(nz!(5)); - let debug = format!("{span:?}"); - - assert!(debug.contains("first_four_bytes"), "expected preview field in {debug}"); - assert!(debug.contains("10203040"), "expected first four bytes preview in {debug}"); - } - - #[test] - fn debug_omits_preview_for_short_spans() { - let mut builder = std_alloc_block::allocate(nz!(3)).into_span_builder(); - - builder.put_u8(0xaa); - builder.put_u8(0xbb); - builder.put_u8(0xcc); - - let span = builder.consume(nz!(3)); - let debug = format!("{span:?}"); - - assert!(debug.contains("Span")); - assert!(!debug.contains("first_four_bytes"), "unexpected preview field in {debug}"); - } } diff --git a/crates/bytesbuf/src/span_builder.rs b/crates/bytesbuf/src/span_builder.rs index e2a12990..79107bec 100644 --- a/crates/bytesbuf/src/span_builder.rs +++ b/crates/bytesbuf/src/span_builder.rs @@ -6,21 +6,15 @@ use std::mem::MaybeUninit; use std::num::NonZero; use std::ptr::NonNull; -use bytes::BufMut; -use bytes::buf::UninitSlice; - use crate::{BlockRef, BlockSize, Span}; -#[cfg(test)] -use bytes::Buf; - /// Owns a mutable span of memory capacity from a memory block, which can be filled with data, /// enabling you to detach spans of immutable bytes from the front to create views over the data. /// -/// Use the [`bytes::buf::BufMut`][1] implementation to fill available memory capacity with data, -/// after which you may detach spans of immutable data from the front via [`consume()`][3]. +/// Use `unfilled_slice_mut()` and `advance()` to fill available memory capacity with data, +/// after which you may detach spans of immutable data from the front via [`consume()`]. /// -/// Filled bytes may be inspected via [`inspect()`][4] to enable a content-based determination +/// Filled bytes may be inspected via [`peek()`] to enable a content-based determination /// to be made on whether (part of) the filled data is ready to be consumed. /// /// # Ownership of memory blocks @@ -31,22 +25,27 @@ use bytes::Buf; /// * Zero or more bytes of mutable memory. /// /// One block may be split into any number of parts, each consisting of either immutable data -/// or mutable memory. +/// or mutable memory. The mutable memory is always at the end of the block - blocks are filled +/// from the start. /// /// These parts are always accessed via either: /// -/// 1. Any number of `Spans` over any number of parts consisting of immutable data. -/// 1. At most one `SpanBuilder` over at most one part consisting of mutable memory, which -/// may be partly partly or fully uninitialized. +/// 1. Any number of `Spans`, each owning an arbitrary range of immutable data from the block. +/// 1. At most one `SpanBuilder`, owning: +/// * At most one range of immutable data from the block. +/// * At most one range of mutable memory from the block. /// /// When a memory block is first put into use, one `SpanBuilder` is created and has exclusive /// ownership of the block. From this builder, callers may detach `Span`s from the front to create /// sub-slices over immutable data of a desired length. The `Span`s may later be cloned/sliced /// without constraints. The `SpanBuilder` retains exclusive ownership of the remaining part -/// of the memory block (the part that has not been detached as a `Span`). +/// of the memory block (the part that has not been detached as a `Span`), though it may surrender +/// some of that exclusivity early via `peek()`, which creates a `Span` while still keeping the +/// data inside the `SpanBuilder`. /// -/// Note: `Span` and `SpanBuilder` are private APIs and not exposed in the public API -/// surface. The public API only works with `BytesView` and `BytesBuf`. +/// Note: `Span` and `SpanBuilder` are private APIs and not exposed in the public API surface. +/// The public API only works with `BytesView` and `BytesBuf`. The only purpose of these types +/// is to implement the internal memory ownership mechanics of `BytesBuf` and `BytesView`. /// /// Memory blocks are reference counted to avoid lifetime parameter pollution and provide /// flexibility in usage. This also implies that we are not using the Rust borrow checker to @@ -55,9 +54,9 @@ use bytes::Buf; /// by the following guarantees: /// /// 1. The only way to write to a memory block is to own a [`SpanBuilder`] that can be used -/// to append data to the block on the fly via `bytes::BufMut` or to read into the block -/// via elementary I/O operations issued to the operating system (typically via participating -/// in a [`BytesBuf`][crate::BytesBuf] vectored read that fills multiple memory blocks simultaneously). +/// to either copy data into the block or to transfer data into it via I/O operations issued +/// to the operating system (typically via participating in a [`BytesBuf`][crate::BytesBuf] +/// vectored read that fills multiple memory blocks simultaneously). /// 2. Reading from a memory block is only possible once the block (or a slice of it) has been /// filled with data and the filled region separated into a [`Span`], detaching it from /// the [`SpanBuilder`]. At this point further mutation of the detached slice is impossible. @@ -70,8 +69,8 @@ use bytes::Buf; /// /// * At most one [`SpanBuilder`] has a `&mut [MaybeUninit]` to the part of the memory /// block that has not yet been filled with data. -/// * At most one [`SpanBuilder`] has a `&[u8]` to the part of the memory block that has already been -/// filled with data (or a sub-slice of it, if parts have been detached from the front). +/// * At most one [`SpanBuilder`] has a `&[u8]` to the part of the memory block that has already +/// been filled with data (or a sub-slice of it, if some bytes have been detached from the front). /// * Any number of [`Span`]s have a `&[u8]` to the part of the memory block that has already /// been filled with data (or sub-slices of it, potentially different sub-slices each). /// @@ -79,29 +78,28 @@ use bytes::Buf; /// [`SpanBuilder`] instances that perform the bookkeeping necessary to implement the /// ownership model. The memory block object itself is ignorant of all this machinery, merely being /// a reference counting structure around the pointer and length that designates the capacity, with -/// one `BlockRef` indicating one reference. Once all `BlockRef` are dropped, the memory provider -/// may reuse the memory block. -/// -/// [1]: https://docs.rs/bytes/latest/bytes/buf/trait.BufMut.html -/// [3]: Self::consume -/// [4]: Self::inspect +/// one `BlockRef` indicating one reference. Once all `BlockRef` instances are dropped, the memory +/// provider it came from will reclaim the memory block for reuse or release. #[derive(Debug)] pub(crate) struct SpanBuilder { + // For the purposes of the `Span` and `SpanBuilder` types, this merely controls the lifecycle + // of the memory block - dropping the last reference will permit the memory block to be + // reclaimed by the memory provider it originates from. block_ref: BlockRef, - // Pointer to the start of the span builder's capacity. This region includes both the memory - // filled with data as well as the memory that remains available to receive data. + // Pointer to the start of the span builder's capacity. This region includes both + // the filled bytes and the available bytes. // - // Any bytes that have been consumed from the span builder are no longer accessible through - // this pointer - they are not considered part of the builder's capacity and instead become - // part of a detached span's capacity. + // Any bytes that have been consumed from the span builder (in the form of `Span` instances) + // are no longer accessible through this pointer - they are not considered part of the + // builder's capacity and instead become part of the detached span's capacity. start: NonNull>, // Number of bytes after `start` that have been filled with data. // Any bytes after this range must be treated as uninitialized. filled_bytes: BlockSize, - // Number of bytes after `start + filled_bytes` that may be filled with data. + // Number of bytes after `start + filled_bytes` that are available to be filled with data. // This range of bytes must be treated as uninitialized. available_bytes: BlockSize, } @@ -109,16 +107,19 @@ pub(crate) struct SpanBuilder { impl SpanBuilder { /// Creates a span builder and gives it exclusive ownership of a memory block. /// - /// The `block_ref` acts as a reference counted handle to the memory block. It may be cloned - /// at any time to share ownership of the memory block with `Span` instances created by - /// the `SpanBuilder`. When the last instance from this family of clones is dropped, the - /// memory capacity associated with the memory block may be released by the memory provider - /// it originates from. + /// The `BlockRef` acts as an `Arc`-style reference counted handle to the memory block. + /// It may be cloned at any time to share ownership of the memory block with `Span` + /// instances created by the `SpanBuilder`. + /// + /// When the last instance from the family of `BlockRef` clones is dropped, the + /// memory capacity associated with the memory block will be reclaimed by the + /// memory provider it originates from. /// /// # Safety /// /// The caller must guarantee that the `SpanBuilder` being created has exclusive ownership - /// of the provided memory blocks (i.e. no `BlockRef` clones referencing the same block exist). + /// of the provided memory blocks. This means that no `BlockRef` clones referencing the + /// same block are permitted to exist at this point. pub(crate) const unsafe fn new(start: NonNull>, len: NonZero, block_ref: BlockRef) -> Self { Self { block_ref, @@ -128,91 +129,64 @@ impl SpanBuilder { } } - /// Number of bytes at the front that have been filled with data. + /// Bytes of data contained in the span builder. + /// + /// These bytes may be consumed, detaching `Span` instances from the front via `consume()`. pub(crate) const fn len(&self) -> BlockSize { self.filled_bytes } + /// Returns `true` if there are no filled bytes in the span builder. + /// + /// There may still be available capacity to fill with data. pub(crate) const fn is_empty(&self) -> bool { self.filled_bytes == 0 } - /// Consumes the specified number of bytes (of already filled data) from the front of the - /// builder's memory block, returning a span with those immutable bytes. + /// How many more bytes of data fit into the span builder. + #[cfg_attr(test, mutants::skip)] // Lying about capacity is a great way to infinite loop. + pub(crate) fn remaining_capacity(&self) -> usize { + self.available_bytes as usize + } + + /// Consumes bytes of data from the front of the span builder. + /// + /// The span builder's memory capacity shrinks by bytes consumed, with the returned bytes + /// no longer having any association with the span builder. /// /// # Panics /// - /// Panics if the requested number of bytes to return exceeds the number of bytes filled - /// with data. + /// Panics if the requested number of bytes is greater than `len()`. pub(crate) fn consume(&mut self, len: NonZero) -> Span { - self.consume_checked(len) - .expect("attempted to consume more bytes than available in builder") - } - - /// Consumes the specified number of bytes (of already filled data) from the front of the - /// builder's memory block, returning a span with those immutable bytes. - /// - /// Returns `None` if the requested number of bytes to return - /// exceeds the number of bytes filled with data. - pub(crate) fn consume_checked(&mut self, len: NonZero) -> Option { - if len.get() > self.filled_bytes { - return None; - } + assert!(len.get() <= self.len()); - // SAFETY: We must guarantee that the region has been initialized. - // Yes, it has - this is guarded by the `filled_bytes` check above. + // SAFETY: We must guarantee that the range to return has been initialized. + // Yes, it has - this is guarded by the `length` check above. let span = unsafe { Span::new(self.start.cast(), len.get(), self.block_ref.clone()) }; - // Do this before moving the pointer, so even if something panicks we do not allow - // out of bounds access via the pointer. - self.filled_bytes = self - .filled_bytes - .checked_sub(len.get()) - .expect("already handled the case where len > filled_bytes"); + // This cannot overflow - guarded by assertion above. + self.filled_bytes = self.filled_bytes.wrapping_sub(len.get()); // SAFETY: We only seeked over filled bytes, so we must still be in-bounds. self.start = unsafe { self.start.add(len.get() as usize) }; - Some(span) + span } - /// Creates a span over the filled data without consuming it from the builder. + /// Creates a `Span` over the data in the span builder without consuming it from the builder. pub(crate) fn peek(&self) -> Span { // SAFETY: The data in the span builder up to `filled_bytes` is initialized. unsafe { Span::new(self.start.cast(), self.filled_bytes, self.block_ref.clone()) } } - /// Allows the underlying memory block to be accessed, primarily used to extend its lifetime - /// beyond that of the `SpanBuilder` itself. + /// References the memory block that provides the span builder's memory capacity. pub(crate) const fn block(&self) -> &BlockRef { &self.block_ref } -} - -// SAFETY: The trait does not clearly state any safety requirements we must satisfy, so it is -// unclear why this trait is marked unsafe. Cross your fingers and hope for the best! -unsafe impl BufMut for SpanBuilder { - #[cfg_attr(test, mutants::skip)] // Lying about remaining capacity is a great way to infinite loop. - fn remaining_mut(&self) -> usize { - self.available_bytes as usize - } - - unsafe fn advance_mut(&mut self, cnt: usize) { - let count = BlockSize::try_from(cnt).expect("attempted to advance past end of span builder"); - - // Decrease the end first, so even if there is a panic we do not allow out of bounds access. - self.available_bytes = self - .available_bytes - .checked_sub(count) - .expect("attempted to advance past end of span builder"); - - self.filled_bytes = self - .filled_bytes - .checked_add(count) - .expect("attempted to advance past end of span builder"); - } - fn chunk_mut(&mut self) -> &mut bytes::buf::UninitSlice { + /// References the available (unfilled) part of the span builder's memory capacity. + #[cfg_attr(test, mutants::skip)] // Risk of UB if this returns wrong slices. + pub(crate) fn unfilled_slice_mut(&mut self) -> &mut [MaybeUninit] { // SAFETY: We are seeking past initialized memory, so at most we are at the end of our // memory block (which is still valid) but cannot exceed it in any way. let available_start = unsafe { self.start.add(self.filled_bytes as usize) }; @@ -220,10 +194,54 @@ unsafe impl BufMut for SpanBuilder { // SAFETY: We are responsible for the pointer pointing to a valid storage of the given type // (guaranteed by memory block) and for the slice having exclusive access to the memory for // the duration of its lifetime (guaranteed by `&mut self` which inherits exclusive access - // from the SpanBuilder itself). - let available_slice = unsafe { slice::from_raw_parts_mut(available_start.as_ptr(), self.available_bytes as usize) }; + // from the SpanBuilder itself, which received such a guarantee in `new()`). While part + // of the span builder's memory may already be shared via `Span` instances created as a + // result of `peek()`, we seek over those ranges and only return the unfilled part here. + unsafe { slice::from_raw_parts_mut(available_start.as_ptr(), self.available_bytes as usize) } + } + + /// Signals that `len` bytes of data has been written into the span builder. + /// + /// # Safety + /// + /// The caller must guarantee that at least `len` bytes of data has been written + /// at the start of the unfilled slice. + /// + /// The caller must guarantee that `len` is less than or equal to `remaining_capacity()`. + pub(crate) unsafe fn advance(&mut self, len: usize) { + #[expect(clippy::cast_possible_truncation, reason = "guaranteed by safety requirements")] + let count = len as BlockSize; + + // Cannot overflow - guaranteed by safety requirements. + self.available_bytes = self.available_bytes.wrapping_sub(count); - UninitSlice::uninit(available_slice) + // Cannot overflow - guaranteed by safety requirements. + self.filled_bytes = self.filled_bytes.wrapping_add(count); + } + + /// Appends a slice of bytes to the span builder. + /// + /// Convenience function for testing purposes, to allow a span builder to be easily + /// filled with test data. In real usage, all filling of data will occur through the + /// methods on `BytesBuf`. + #[cfg(test)] + pub(crate) fn put_slice(&mut self, src: &[u8]) { + use std::ptr; + + let len = src.len(); + + assert!(self.remaining_capacity() >= len); + + let dest_slice = self.unfilled_slice_mut(); + + // SAFETY: Both are byte slices, so no alignment concerns. + // We verified length is in bounds above. + unsafe { + ptr::copy_nonoverlapping(src.as_ptr(), dest_slice.as_mut_ptr().cast(), len); + } + + // SAFETY: We indeed filled this many bytes and verified there was enough capacity. + unsafe { self.advance(len) }; } } @@ -231,112 +249,75 @@ unsafe impl BufMut for SpanBuilder { // state is thread-mobile. unsafe impl Send for SpanBuilder {} // SAFETY: The presence of pointers disables Sync but we re-enable it here because all our internal -// state is thread-safe (though only for reads - we still require outer mutability). +// state is thread-safe (though only for reads - we still require outer mutability, which disables +// multithreaded mutation). unsafe impl Sync for SpanBuilder {} #[cfg(test)] mod tests { use new_zealand::nz; use static_assertions::assert_impl_all; - use testing_aids::assert_panic; use super::*; use crate::std_alloc_block; + // The type is thread-mobile (Send) and can be shared (for reads) between threads (Sync). + assert_impl_all!(SpanBuilder: Send, Sync); + #[test] fn smoke_test() { let mut builder = std_alloc_block::allocate(nz!(10)).into_span_builder(); assert_eq!(builder.len(), 0); assert!(builder.is_empty()); - assert_eq!(builder.remaining_mut(), 10); + assert_eq!(builder.remaining_capacity(), 10); - assert!(builder.consume_checked(nz!(1)).is_none()); - - builder.put_u64(1234); + builder.put_slice(&1234_u64.to_ne_bytes()); assert_eq!(builder.len(), 8); assert!(!builder.is_empty()); - assert_eq!(builder.remaining_mut(), 2); + assert_eq!(builder.remaining_capacity(), 2); _ = builder.consume(nz!(8)); assert_eq!(builder.len(), 0); assert!(builder.is_empty()); - assert_eq!(builder.remaining_mut(), 2); + assert_eq!(builder.remaining_capacity(), 2); - builder.put_u16(1234); + builder.put_slice(&1234_u16.to_ne_bytes()); assert_eq!(builder.len(), 2); assert!(!builder.is_empty()); - assert_eq!(builder.remaining_mut(), 0); + assert_eq!(builder.remaining_capacity(), 0); _ = builder.consume(nz!(2)); assert_eq!(builder.len(), 0); assert!(builder.is_empty()); - assert_eq!(builder.remaining_mut(), 0); + assert_eq!(builder.remaining_capacity(), 0); } #[test] - fn inspect() { + fn peek() { let mut builder = std_alloc_block::allocate(nz!(10)).into_span_builder(); - builder.put_u32(1234); - builder.put_u32(5678); - builder.put_u16(90); - - assert_eq!(builder.len(), 10); - assert_eq!(builder.remaining_mut(), 0); + builder.put_slice(&1234_u32.to_ne_bytes()); + builder.put_slice(&5678_u32.to_ne_bytes()); + builder.put_slice(&90_u16.to_ne_bytes()); let mut peeked = builder.peek(); - assert_eq!(peeked.remaining(), 10); - assert_eq!(peeked.chunk().len(), 10); + assert_eq!(peeked.len(), 10); + assert_eq!(peeked.as_ref().len(), 10); - assert_eq!(peeked.get_u32(), 1234); - assert_eq!(peeked.get_u32(), 5678); - assert_eq!(peeked.get_u16(), 90); + assert_eq!(u32::from_ne_bytes(peeked.get_array()), 1234); + assert_eq!(u32::from_ne_bytes(peeked.get_array()), 5678); + assert_eq!(u16::from_ne_bytes(peeked.get_array()), 90); - assert_eq!(peeked.remaining(), 0); - assert_eq!(peeked.chunk().len(), 0); - - assert_eq!(builder.len(), 10); - assert_eq!(builder.remaining_mut(), 0); + assert_eq!(peeked.len(), 0); + assert_eq!(peeked.as_ref().len(), 0); _ = builder.consume(nz!(10)); - - assert_eq!(builder.len(), 0); - assert_eq!(builder.remaining_mut(), 0); - } - - #[test] - fn append_oob_panics() { - let mut builder = std_alloc_block::allocate(nz!(10)).into_span_builder(); - - builder.put_u32(1234); - builder.put_u32(5678); - assert_panic!(builder.put_u32(90)); // Tries to append 4 when only 2 bytes available. - } - - #[test] - fn inspect_oob_panics() { - let mut builder = std_alloc_block::allocate(nz!(10)).into_span_builder(); - - builder.put_u32(1234); - builder.put_u32(5678); - builder.put_u16(90); - - let mut peeked = builder.peek(); - assert_eq!(peeked.get_u32(), 1234); - assert_eq!(peeked.get_u32(), 5678); - assert_panic!(_ = peeked.get_u32()); // Tries to read 4 when only 2 bytes remaining. - } - - #[test] - fn thread_safe_type() { - // The type is thread-mobile (Send) and can be shared (for reads) between threads (Sync). - assert_impl_all!(SpanBuilder: Send, Sync); } #[test] diff --git a/crates/bytesbuf/src/transparent.rs b/crates/bytesbuf/src/transparent.rs index dcfff8dc..e5807510 100644 --- a/crates/bytesbuf/src/transparent.rs +++ b/crates/bytesbuf/src/transparent.rs @@ -86,7 +86,6 @@ fn reserve(min_bytes: usize) -> crate::BytesBuf { #[cfg_attr(coverage_nightly, coverage(off))] #[cfg(test)] mod tests { - use bytes::BufMut; use static_assertions::assert_impl_all; use super::*; @@ -106,12 +105,12 @@ mod tests { assert_eq!(sb.capacity(), 1313); - sb.put_bytes(3, 1313); + sb.put_byte_repeated(3, 1313); let sequence = sb.consume_all(); assert_eq!(sequence.len(), 1313); - assert_eq!(sequence.chunk().len(), 1313); + assert_eq!(sequence.first_slice().len(), 1313); } #[test] diff --git a/crates/bytesbuf/src/view.rs b/crates/bytesbuf/src/view.rs index 442635aa..518027f9 100644 --- a/crates/bytesbuf/src/view.rs +++ b/crates/bytesbuf/src/view.rs @@ -8,40 +8,37 @@ use std::num::NonZero; use std::ops::{Bound, RangeBounds}; use std::{iter, mem}; -use bytes::{Buf, BufMut, Bytes, BytesMut}; use nm::{Event, Magnitude}; use smallvec::SmallVec; +use crate::read_adapter::BytesViewReader; use crate::{BlockSize, MAX_INLINE_SPANS, Memory, MemoryGuard, Span}; -/// A sequence of immutable bytes that can be inspected and consumed. +/// A view over a sequence of immutable bytes. /// -/// Note that only the contents of a sequence are immutable - the sequence itself can be -/// mutated in terms of progressively marking its contents as consumed until it becomes empty. -/// The typical mechanism for consuming the contents of a `BytesView` is the [`bytes::buf::Buf`][1] -/// trait that it implements. +/// Only the contents are immutable - the view itself can be mutated in terms of progressively +/// marking the byte sequence as consumed until the view becomes empty. /// -/// To create a `BytesView`, use a [`BytesBuf`][3] or clone/slice an existing `BytesView`. +/// To create a `BytesView`, use a [`BytesBuf`] or clone/slice an existing `BytesView`. #[doc = include_str!("../doc/snippets/sequence_memory_layout.md")] /// -/// [1]: https://docs.rs/bytes/latest/bytes/buf/trait.Buf.html -/// [3]: crate::BytesBuf +/// [`BytesBuf`]: crate::BytesBuf #[derive(Clone, Debug)] pub struct BytesView { - /// The spans of the sequence, stored in reverse order for efficient consumption + /// The spans of the byte sequence, stored in reverse order for efficient consumption /// by popping items off the end of the collection. - spans_reversed: SmallVec<[Span; MAX_INLINE_SPANS]>, + pub(crate) spans_reversed: SmallVec<[Span; MAX_INLINE_SPANS]>, /// We cache the length so we do not have to recalculate it every time it is queried. len: usize, } impl BytesView { - /// Returns an empty sequence. + /// Creates a view over a zero-sized byte sequence. /// - /// Use a [`BytesBuf`][1] to create a sequence that contains data. + /// Use a [`BytesBuf`] to create a view over some actual data. /// - /// [1]: crate::BytesBuf + /// [`BytesBuf`]: crate::BytesBuf #[cfg_attr(test, mutants::skip)] // Generates no-op mutations, not useful. #[must_use] pub const fn new() -> Self { @@ -56,16 +53,19 @@ impl BytesView { spans_reversed.iter().for_each(|span| assert!(!span.is_empty())); // We can use this to fine-tune the inline span count once we have real-world data. - SEQUENCE_CREATED_SPANS.with(|x| x.observe(spans_reversed.len())); + VIEW_CREATED_SPANS.with(|x| x.observe(spans_reversed.len())); - let len = spans_reversed.iter().map(bytes::Buf::remaining).sum(); + let len = spans_reversed.iter().fold(0_usize, |acc, span: &Span| { + acc.checked_add(span.len() as usize) + .expect("attempted to create a BytesView larger than usize::MAX bytes") + }); Self { spans_reversed, len } } - /// Concatenates a number of spans, yielding a sequence that combines the spans. + /// (For testing) Concatenates a number of spans, yielding a view that combines the spans. /// - /// Later changes made to the input spans will not be reflected in the output sequence. + /// Later changes made to the input spans will not be reflected in the resulting view. #[cfg(test)] pub(crate) fn from_spans(spans: I) -> Self where @@ -77,10 +77,14 @@ impl BytesView { Self::from_spans_reversed(spans_reversed) } - /// Concatenates a number of existing sequences, yielding a combined view. + /// Concatenates a number of existing byte sequences, yielding a combined view. + /// + /// Later changes made to the input views will not be reflected in the resulting view. + /// + /// # Panics /// - /// Later changes made to the input sequences will not be reflected in the output sequence. - pub fn from_sequences(sequences: I) -> Self + /// Panics if the resulting view would be larger than `usize::MAX` bytes. + pub fn from_views(views: I) -> Self where I: IntoIterator, ::IntoIter: iter::DoubleEndedIterator, @@ -90,86 +94,92 @@ impl BytesView { // advance. If we had the span count here, we could avoid some allocations. // For a given input ABC123. - let spans_reversed: SmallVec<_> = sequences + let spans_reversed: SmallVec<_> = views .into_iter() - // We first reverse the sequences: 123ABC. + // We first reverse the views: 123ABC. .rev() - // And from inside each sequence we take the reversed spans: 321CBA. - .flat_map(|seq| seq.spans_reversed) + // And from inside each view we take the reversed spans: 321CBA. + .flat_map(|view| view.spans_reversed) // Which become our final SmallVec of spans. Great success! .collect(); - // We can use this to fine-tune the inline span count once we have real-world data. - SEQUENCE_CREATED_SPANS.with(|x| x.observe(spans_reversed.len())); - - let len = spans_reversed.iter().map(bytes::Buf::remaining).sum(); - - Self { spans_reversed, len } + Self::from_spans_reversed(spans_reversed) } - /// Shorthand to copy a byte slice into a new `BytesView`, which is a common operation. + /// Creates a `BytesView` by copying the contents of a `&[u8]`. + /// + /// # Reusing existing data + /// + /// There is intentionally no mechanism in `bytesbuf` to reference an existing `&[u8]` + /// without copying, even if `'static`, because high-performance I/O requires all data + /// to exist in memory owned by the I/O subsystem. Reusing arbitrary byte slices is + /// not supported in order to discourage design practices that would work against this + /// efficiency goal. + /// + /// To reuse memory allocations, reuse the `BytesView` itself. See the `bb_reuse.rs` + /// example in the `bytesbuf` source code for an example of how to do this efficiently. #[must_use] pub fn copied_from_slice(bytes: &[u8], memory_provider: &impl Memory) -> Self { - let mut buffer = memory_provider.reserve(bytes.len()); - buffer.put_slice(bytes); - buffer.consume_all() + let mut buf = memory_provider.reserve(bytes.len()); + buf.put_slice(bytes); + buf.consume_all() } pub(crate) fn into_spans_reversed(self) -> SmallVec<[Span; MAX_INLINE_SPANS]> { self.spans_reversed } - /// The number of bytes remaining in the sequence. + /// The number of bytes exposed through the view. + /// + /// Consuming bytes from the view reduces its length. #[must_use] pub fn len(&self) -> usize { // Sanity check. - debug_assert_eq!(self.len, self.spans_reversed.iter().map(bytes::Buf::remaining).sum::()); + debug_assert_eq!(self.len, self.spans_reversed.iter().map(|x| x.len() as usize).sum::()); self.len } - /// Whether the sequence is empty. + /// Whether the view is of a zero-sized byte sequence. #[must_use] pub fn is_empty(&self) -> bool { self.len() == 0 } - /// Creates a memory guard that extends the lifetime of the memory blocks that provide the - /// backing memory capacity for this sequence. + /// Extends the lifetime of the memory capacity backing this view. /// /// This can be useful when unsafe code is used to reference the contents of a `BytesView` and it /// is possible to reach a condition where the `BytesView` itself no longer exists, even though - /// the contents are referenced (e.g. because this is happening in non-Rust code). + /// the contents are referenced (e.g. because the remaining references are in non-Rust code). pub fn extend_lifetime(&self) -> MemoryGuard { MemoryGuard::new(self.spans_reversed.iter().map(Span::block_ref).map(Clone::clone)) } - /// Returns a sub-sequence of the sequence without consuming any data in the original. + /// Returns a range of the byte sequence. /// - /// The bounds logic only considers data currently present in the sequence. - /// Any data already consumed is not considered part of the sequence. + /// The bounds logic only considers data currently present in the view. + /// Any data already consumed is not considered part of the view. /// /// # Panics /// - /// Panics if the provided range is outside the bounds of the sequence. + /// Panics if the provided range is outside the bounds of the view. #[must_use] - pub fn slice(&self, range: R) -> Self + pub fn range(&self, range: R) -> Self where R: RangeBounds, { - self.slice_checked(range).expect("provided range out of sequence bounds") + self.range_checked(range).expect("provided range out of view bounds") } - /// Returns a sub-sequence of the sequence without consuming any data in the original, - /// or `None` if the range is outside the bounds of the sequence. + /// Returns a range of the byte sequence or `None` if out of bounds. /// - /// The bounds logic only considers data currently present in the sequence. - /// Any data already consumed is not considered part of the sequence. + /// The bounds logic only considers data currently present in the view. + /// Any data already consumed is not considered part of the view. #[must_use] #[expect(clippy::missing_panics_doc, reason = "only unreachable panics")] #[expect(clippy::too_many_lines, reason = "acceptable for now")] #[cfg_attr(test, mutants::skip)] // Mutations include impossible conditions that we cannot test as well as mutations that are functionally equivalent. - pub fn slice_checked(&self, range: R) -> Option + pub fn range_checked(&self, range: R) -> Option where R: RangeBounds, { @@ -356,187 +366,176 @@ impl BytesView { }) } - /// Executes a function `f` on each chunk in the sequence, in order, - /// and marks the entire sequence as consumed. - pub fn consume_all_chunks(&mut self, mut f: F) + /// Executes a function `f` on each slice, consuming them all. + /// + /// The slices that make up the view are iterated in order, + /// providing each to `f`. The view becomes empty after this. + pub fn consume_all_slices(&mut self, mut f: F) where F: FnMut(&[u8]), { + // TODO: This fn could just be .into_iter() - we have no real + // need for the "consume pattern" here. Iterators are more idiomatic. while !self.is_empty() { - f(self.chunk()); - self.advance(self.chunk().len()); + let slice = self.first_slice(); + f(slice); + self.advance(slice.len()); } } - /// Consumes the sequence and returns an instance of `Bytes`. - /// - /// We do not expose `From for Bytes` because this is not guaranteed to be a cheap - /// operation and may involve data copying, so `.into_bytes()` must be explicitly called to - /// make the conversion obvious. - /// - /// # Performance - /// - /// This operation is zero-copy if the sequence is backed by a single consecutive - /// span of memory. + /// References the first slice of bytes in the byte sequence. /// - /// If the sequence is backed by multiple spans of memory, the data will be copied - /// to a new `Bytes` instance backed by memory capacity from the Rust global allocator. - #[must_use] - #[expect(clippy::missing_panics_doc, reason = "only unreachable panics")] - pub fn into_bytes(self) -> Bytes { - if self.spans_reversed.is_empty() { - INTO_BYTES_SHARED.with(|x| x.observe(0)); - - Bytes::new() - } else if self.spans_reversed.len() == 1 { - // We are a single-span sequence, which can always be zero-copy represented. - INTO_BYTES_SHARED.with(|x| x.observe(self.len())); - - self.spans_reversed.first().expect("we verified there is one span").to_bytes() - } 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()); - - INTO_BYTES_COPIED.with(|x| x.observe(self.len())); - - bytes.freeze() - } - } - - /// Returns the first consecutive chunk of bytes in the byte sequence. - /// - /// There are no guarantees on the length of each chunk. In a non-empty sequence, - /// each chunk may contain anywhere between 1 byte and all bytes of the sequence. - /// - /// Returns an empty slice if the sequence is empty. + /// Returns an empty slice if the view is over a zero-sized byte sequence. + #[doc = include_str!("../doc/snippets/sequence_memory_layout.md")] #[cfg_attr(test, mutants::skip)] // Mutating this can cause infinite loops. #[must_use] - pub fn chunk(&self) -> &[u8] { + pub fn first_slice(&self) -> &[u8] { self.spans_reversed.last().map_or::<&[u8], _>(&[], |span| span) } - /// Fills an array of `IoSlice` with chunks from the start of the sequence. + /// Fills an array with [`IoSlice`]s representing this view. + /// + /// Returns the number of elements written into `dst`. If there is not enough space in `dst` + /// to represent the entire view, only as many slices as fit will be written. /// - /// See also [`chunks_as_slices_vectored()`][1] for a version that fills an array of slices - /// instead of `IoSlice`. + /// See also [`slices()`] for a version that fills an array of regular slices instead of [`IoSlice`]s. + #[doc = include_str!("../doc/snippets/sequence_memory_layout.md")] /// - /// [1]: Self::chunks_as_slices_vectored + /// [`slices()`]: Self::slices #[expect(clippy::missing_panics_doc, reason = "only unreachable panics")] - pub fn chunks_vectored<'a>(&'a self, dst: &mut [IoSlice<'a>]) -> usize { + pub fn io_slices<'a>(&'a self, dst: &mut [IoSlice<'a>]) -> usize { if dst.is_empty() { return 0; } - // How many chunks can we fill? - let chunk_count = self.spans_reversed.len().min(dst.len()); + // How many slices can we fill? + let slice_count = self.spans_reversed.len().min(dst.len()); // Note that IoSlice has a length limit of u32::MAX. Our spans are also limited to u32::MAX // by memory manager internal limits (MAX_BLOCK_SIZE), so this is safe. - for (i, span) in self.spans_reversed.iter().rev().take(chunk_count).enumerate() { + for (i, span) in self.spans_reversed.iter().rev().take(slice_count).enumerate() { *dst.get_mut(i).expect("guarded by min()") = IoSlice::new(span); } - chunk_count + slice_count } - /// Fills an array of slices with chunks from the start of the sequence. + /// Fills an array with byte slices representing this view. /// - /// This is equivalent to [`chunks_vectored()`][1] but as regular slices, without the `IoSlice` - /// wrapper, which can be unnecessary/limiting and is not always desirable. + /// Returns the number of elements written into `dst`. If there is not enough space in `dst` + /// to represent the entire view, only as many slices as fit will be written. /// - /// [1]: Self::chunks_vectored + /// See also [`io_slices()`] for a version that fills an array of [`IoSlice`]s instead of regular byte slices. + #[doc = include_str!("../doc/snippets/sequence_memory_layout.md")] + /// + /// [`io_slices()`]: Self::io_slices #[expect(clippy::missing_panics_doc, reason = "only unreachable panics")] - pub fn chunks_as_slices_vectored<'a>(&'a self, dst: &mut [&'a [u8]]) -> usize { + pub fn slices<'a>(&'a self, dst: &mut [&'a [u8]]) -> usize { if dst.is_empty() { return 0; } - // How many chunks can we fill? - let chunk_count = self.spans_reversed.len().min(dst.len()); + // How many slices can we fill? + let slice_count = self.spans_reversed.len().min(dst.len()); - for (i, span) in self.spans_reversed.iter().rev().take(chunk_count).enumerate() { + for (i, span) in self.spans_reversed.iter().rev().take(slice_count).enumerate() { *dst.get_mut(i).expect("guarded by min()") = span; } - chunk_count + slice_count } - /// Inspects the metadata of the current chunk of memory referenced by `chunk()`. + /// Inspects the metadata of the [`first_slice()`]. + /// + /// `None` if there is no metadata associated with the first slice or + /// if the view is over a zero-sized byte sequence. /// - /// `None` if there is no metadata associated with the chunk or if the sequence is empty. + /// [`first_slice()`]: Self::first_slice #[must_use] - pub fn chunk_meta(&self) -> Option<&dyn Any> { + pub fn first_slice_meta(&self) -> Option<&dyn Any> { self.spans_reversed.last().and_then(|span| span.block_ref().meta()) } - /// Iterates over the metadata of all the chunks in the sequence. + /// Iterates over the metadata of all the slices that make up the view. /// - /// A chunk is any consecutive span of memory that would be returned by `chunk()` at some point - /// during the consumption of a [`BytesView`]. + /// Each slice iterated over is a slice that would be returned by [`first_slice()]` + /// at some point during the complete consumption of the data covered by a `BytesView`. /// /// You may wish to iterate over the metadata to determine in advance which implementation /// strategy to use for a function, depending on what the metadata indicates about the /// configuration of the memory blocks backing the byte sequence. - pub fn iter_chunk_metas(&self) -> BytesViewChunkMetasIterator<'_> { - BytesViewChunkMetasIterator::new(self) + /// + /// [`first_slice()`]: Self::first_slice + pub fn iter_slice_metas(&self) -> BytesViewSliceMetasIterator<'_> { + BytesViewSliceMetasIterator::new(self) } - /// Marks the first `count` bytes of the sequence as consumed, dropping them from the sequence. + /// Marks the first `count` bytes as consumed. + /// + /// The consumed bytes are dropped from the view, moving any remaining bytes to the front. /// /// # Panics /// - /// Panics if `count` is greater than the number of bytes remaining in the sequence. + /// Panics if `count` is greater than the number of bytes remaining. #[cfg_attr(test, mutants::skip)] // Mutating this can cause infinite loops. pub fn advance(&mut self, mut count: usize) { - self.len = self.len.checked_sub(count).expect("attempted to advance past end of sequence"); + self.len = self.len.checked_sub(count).expect("attempted to advance past end of the view"); while count > 0 { let front = self .spans_reversed .last_mut() .expect("logic error - ran out of spans before advancing over their contents"); - let remaining = front.remaining(); + let span_len = front.len() as usize; - if count < remaining { - front.advance(count); + if count < span_len { + // SAFETY: We must guarantee we advance in-bounds. The if statement guarantees that. + unsafe { + front.advance(count); + } break; } self.spans_reversed.pop(); - count = count.checked_sub(remaining).expect("already handled count < remaining case"); + // Will never overflow because we already handled the count < span_len case. + count = count.wrapping_sub(span_len); } } - /// Appends another byte sequence to the end of this one. + /// Appends another view to the end of this one. + /// + /// This is a zero-copy operation, reusing the memory capacity of the other view. /// /// # Panics /// - /// Panics if the resulting sequence would be larger than `usize::MAX` bytes. + /// Panics if the resulting view would be larger than `usize::MAX` bytes. pub fn append(&mut self, other: Self) { self.len = self .len .checked_add(other.len) - .expect("attempted to create a byte sequence larger than usize::MAX bytes"); + .expect("attempted to create a BytesView larger than usize::MAX bytes"); self.spans_reversed.insert_many(0, other.spans_reversed); } - /// Returns a new byte sequence that concatenates this byte sequence with another. + /// Returns a new view that concatenates this view with another. + /// + /// This is a zero-copy operation, reusing the memory capacity of the other view. /// /// # Panics /// - /// Panics if the resulting sequence would be larger than `usize::MAX` bytes. + /// Panics if the resulting view would be larger than `usize::MAX` bytes. #[must_use] pub fn concat(&self, other: Self) -> Self { - let mut new_sequence = self.clone(); - new_sequence.append(other); - new_sequence + let mut new_view = self.clone(); + new_view.append(other); + new_view + } + + /// Exposes the instance through the [`Read`][std::io::Read] trait. + #[must_use] + pub fn as_read(&mut self) -> impl std::io::Read { + BytesViewReader::new(self) } } @@ -546,71 +545,49 @@ impl Default for BytesView { } } -impl Buf for BytesView { - #[cfg_attr(test, mutants::skip)] // Trivial forwarder. - fn remaining(&self) -> usize { - self.len() - } - - #[cfg_attr(test, mutants::skip)] // Trivial forwarder. - fn chunk(&self) -> &[u8] { - self.chunk() - } - - #[cfg_attr(test, mutants::skip)] // Trivial forwarder. - fn chunks_vectored<'a>(&'a self, dst: &mut [IoSlice<'a>]) -> usize { - self.chunks_vectored(dst) - } - - #[cfg_attr(test, mutants::skip)] // Trivial forwarder. - fn advance(&mut self, cnt: usize) { - self.advance(cnt); - } -} - impl PartialEq for BytesView { fn eq(&self, other: &Self) -> bool { // We do not care about the structure, only the contents. - if self.remaining() != other.remaining() { + if self.len() != other.len() { return false; } - let mut remaining_bytes = self.remaining(); + let mut remaining_bytes = self.len(); - // The two sequences may have differently sized spans, so we only compare in steps - // of the smallest span size offered by either sequences. + // The two views may have differently sized spans, so we only compare in steps + // of the smallest span size offered by either view. - // We clone the sequences to create temporary views that we slide over the contents. + // We clone the views to create windows that we slide over the contents. let mut self_view = self.clone(); let mut other_view = other.clone(); while remaining_bytes > 0 { - let self_chunk = self_view.chunk(); - let other_chunk = other_view.chunk(); + let self_slice = self_view.first_slice(); + let other_slice = other_view.first_slice(); - let chunk_size = NonZero::new(self_chunk.len().min(other_chunk.len())) - .expect("both sequences said there are remaining bytes but we got an empty chunk"); + let comparison_len = NonZero::new(self_slice.len().min(other_slice.len())) + .expect("both views said there are remaining bytes but we got an empty slice from at least one of them"); - let self_slice = self_chunk.get(..chunk_size.get()).expect("already checked that remaining > 0"); - let other_slice = other_chunk.get(..chunk_size.get()).expect("already checked that remaining > 0"); + let self_slice = self_slice.get(..comparison_len.get()).expect("already checked that remaining > 0"); + let other_slice = other_slice.get(..comparison_len.get()).expect("already checked that remaining > 0"); if self_slice != other_slice { // Something is different. That is enough for a determination. return false; } - // Advance both sequences by the same amount. - self_view.advance(chunk_size.get()); - other_view.advance(chunk_size.get()); + // Advance both views by the same amount. + self_view.advance(comparison_len.get()); + other_view.advance(comparison_len.get()); remaining_bytes = remaining_bytes - .checked_sub(chunk_size.get()) + .checked_sub(comparison_len.get()) .expect("impossible to consume more bytes from the sequences than are remaining"); } debug_assert_eq!(remaining_bytes, 0); - debug_assert_eq!(self_view.remaining(), 0); - debug_assert_eq!(other_view.remaining(), 0); + debug_assert_eq!(self_view.len(), 0); + debug_assert_eq!(other_view.len(), 0); true } @@ -622,22 +599,22 @@ impl PartialEq<&[u8]> for BytesView { // We do not care about the structure, only the contents. - if self.remaining() != other.len() { + if self.len() != other.len() { return false; } - let mut remaining_bytes = self.remaining(); + let mut remaining_bytes = self.len(); // We clone the sequence to create a temporary view that we slide over the contents. let mut self_view = self.clone(); while remaining_bytes > 0 { - let self_chunk = self_view.chunk(); - let chunk_size = - NonZero::new(self_chunk.len()).expect("both sequences said there are remaining bytes but we got an empty chunk"); + let self_slice = self_view.first_slice(); + let slice_size = NonZero::new(self_slice.len()) + .expect("both sides of the comparison said there are remaining bytes but we got an empty slice from at least one of them"); - let self_slice = self_chunk.get(..chunk_size.get()).expect("already checked that remaining > 0"); - let other_slice = other.get(..chunk_size.get()).expect("already checked that remaining > 0"); + let self_slice = self_slice.get(..slice_size.get()).expect("already checked that remaining > 0"); + let other_slice = other.get(..slice_size.get()).expect("already checked that remaining > 0"); if self_slice != other_slice { // Something is different. That is enough for a determination. @@ -645,16 +622,16 @@ impl PartialEq<&[u8]> for BytesView { } // Advance the sequence by the same amount. - self_view.advance(chunk_size.get()); - other = other.get(chunk_size.get()..).expect("guarded by min() above"); + self_view.advance(slice_size.get()); + other = other.get(slice_size.get()..).expect("guarded by min() above"); remaining_bytes = remaining_bytes - .checked_sub(chunk_size.get()) + .checked_sub(slice_size.get()) .expect("impossible to consume more bytes from the sequences than are remaining"); } debug_assert_eq!(remaining_bytes, 0); - debug_assert_eq!(self_view.remaining(), 0); + debug_assert_eq!(self_view.len(), 0); debug_assert_eq!(other.len(), 0); true @@ -679,40 +656,40 @@ impl PartialEq for &[u8; LEN] { } } -/// Iterator over all `chunk_meta()` results that a [`BytesView`] may return. +/// Iterator over all `first_slice_meta()` values of a [`BytesView`]. /// -/// Returned by [`BytesView::iter_chunk_metas()`][BytesView::iter_chunk_metas] and allows you to -/// inspect the metadata of each chunk in the sequence without first consuming previous chunks. +/// Returned by [`BytesView::iter_slice_metas()`][BytesView::iter_slice_metas] and allows you to +/// inspect the metadata of each slice that makes up the view without consuming any part of the view. #[must_use] #[derive(Debug)] -pub struct BytesViewChunkMetasIterator<'s> { - // This starts off as a clone of the parent sequence, just for ease of implementation. - // We consume the parts of the sequence we have already iterated over. - sequence: BytesView, +pub struct BytesViewSliceMetasIterator<'s> { + // This starts off as a clone of the view, just for ease of implementation. + // We consume the parts of the view we have already iterated over. + view: BytesView, - // We keep a reference to the sequence we are iterating over, even though + // We keep a reference to the view we are iterating over, even though // the current implementation does not use it (because a future one might). _parent: PhantomData<&'s BytesView>, } -impl<'s> BytesViewChunkMetasIterator<'s> { - pub(crate) fn new(sequence: &'s BytesView) -> Self { +impl<'s> BytesViewSliceMetasIterator<'s> { + pub(crate) fn new(view: &'s BytesView) -> Self { Self { - sequence: sequence.clone(), + view: view.clone(), _parent: PhantomData, } } } -impl<'s> Iterator for BytesViewChunkMetasIterator<'s> { +impl<'s> Iterator for BytesViewSliceMetasIterator<'s> { type Item = Option<&'s dyn Any>; fn next(&mut self) -> Option { - if self.sequence.is_empty() { + if self.view.is_empty() { return None; } - let meta = self.sequence.chunk_meta(); + let meta = self.view.first_slice_meta(); // SAFETY: It is normally not possible to return a self-reference from an iterator because // next() only has an implicit lifetime for `&self`, which cannot be named in `Item`. @@ -726,7 +703,7 @@ impl<'s> Iterator for BytesViewChunkMetasIterator<'s> { let meta_with_s = unsafe { mem::transmute::, Option<&'s dyn Any>>(meta) }; // Seek forward to the next chunk before we return. - self.sequence.advance(self.sequence.chunk().len()); + self.view.advance(self.view.first_slice().len()); Some(meta_with_s) } @@ -735,20 +712,13 @@ impl<'s> Iterator for BytesViewChunkMetasIterator<'s> { const SPAN_COUNT_BUCKETS: &[Magnitude] = &[0, 1, 2, 4, 8, 16, 32]; thread_local! { - static SEQUENCE_CREATED_SPANS: Event = Event::builder() - .name("sequence_created_spans") + static VIEW_CREATED_SPANS: Event = Event::builder() + .name("bytesbuf_view_created_spans") .histogram(SPAN_COUNT_BUCKETS) .build(); - - static INTO_BYTES_SHARED: Event = Event::builder() - .name("sequence_into_bytes_shared") - .build(); - - static INTO_BYTES_COPIED: Event = Event::builder() - .name("sequence_into_bytes_copied") - .build(); } +#[cfg_attr(coverage_nightly, coverage(off))] #[cfg(test)] mod tests { #![allow( @@ -769,61 +739,63 @@ mod tests { use crate::testing::TestMemoryBlock; use crate::{BytesBuf, TransparentTestMemory, std_alloc_block}; + assert_impl_all!(BytesView: Send, Sync); + #[test] fn smoke_test() { - let mut builder = std_alloc_block::allocate(nz!(10)).into_span_builder(); + let mut span_builder = std_alloc_block::allocate(nz!(10)).into_span_builder(); - builder.put_u64(1234); - builder.put_u16(16); + span_builder.put_slice(&1234_u64.to_ne_bytes()); + span_builder.put_slice(&16_u16.to_ne_bytes()); - let span1 = builder.consume(nz!(4)); - let span2 = builder.consume(nz!(3)); - let span3 = builder.consume(nz!(3)); + let span1 = span_builder.consume(nz!(4)); + let span2 = span_builder.consume(nz!(3)); + let span3 = span_builder.consume(nz!(3)); - assert_eq!(0, builder.remaining_mut()); - assert_eq!(span1.remaining(), 4); - assert_eq!(span2.remaining(), 3); - assert_eq!(span3.remaining(), 3); + assert_eq!(0, span_builder.remaining_capacity()); + assert_eq!(span1.len(), 4); + assert_eq!(span2.len(), 3); + assert_eq!(span3.len(), 3); - let mut sequence = BytesView::from_spans(vec![span1, span2, span3]); + let mut view = BytesView::from_spans(vec![span1, span2, span3]); - assert!(!sequence.is_empty()); - assert_eq!(10, sequence.remaining()); + assert!(!view.is_empty()); + assert_eq!(10, view.len()); - let slice = sequence.chunk(); + let slice = view.first_slice(); assert_eq!(4, slice.len()); // We read 8 bytes here, so should land straight inside span3. - assert_eq!(sequence.get_u64(), 1234); + assert_eq!(view.get_num_ne::(), 1234); - assert_eq!(2, sequence.remaining()); + assert_eq!(2, view.len()); - let slice = sequence.chunk(); + let slice = view.first_slice(); assert_eq!(2, slice.len()); - assert_eq!(sequence.get_u16(), 16); + assert_eq!(view.get_num_ne::(), 16); - assert_eq!(0, sequence.remaining()); - assert!(sequence.is_empty()); + assert_eq!(0, view.len()); + assert!(view.is_empty()); } #[test] fn oob_is_panic() { - let mut builder = std_alloc_block::allocate(nz!(10)).into_span_builder(); + let mut span_builder = std_alloc_block::allocate(nz!(10)).into_span_builder(); - builder.put_u64(1234); - builder.put_u16(16); + span_builder.put_slice(&1234_u64.to_ne_bytes()); + span_builder.put_slice(&16_u16.to_ne_bytes()); - let span1 = builder.consume(nz!(4)); - let span2 = builder.consume(nz!(3)); - let span3 = builder.consume(nz!(3)); + let span1 = span_builder.consume(nz!(4)); + let span2 = span_builder.consume(nz!(3)); + let span3 = span_builder.consume(nz!(3)); - let mut sequence = BytesView::from_spans(vec![span1, span2, span3]); + let mut view = BytesView::from_spans(vec![span1, span2, span3]); - assert_eq!(10, sequence.remaining()); + assert_eq!(10, view.len()); - assert_eq!(sequence.get_u64(), 1234); - assert_panic!(_ = sequence.get_u32()); // Reads 4 but only has 2 remaining. + assert_eq!(view.get_num_ne::(), 1234); + assert_panic!(_ = view.get_num_ne::()); // Reads 4 but only has 2 remaining. } #[test] @@ -842,19 +814,19 @@ mod tests { let guard = { // SAFETY: We guarantee exclusive access to the memory capacity. - let mut builder1 = unsafe { block1.as_ref().to_block() }.into_span_builder(); + let mut span_builder1 = unsafe { block1.as_ref().to_block() }.into_span_builder(); // SAFETY: We guarantee exclusive access to the memory capacity. - let mut builder2 = unsafe { block2.as_ref().to_block() }.into_span_builder(); + let mut span_builder2 = unsafe { block2.as_ref().to_block() }.into_span_builder(); - builder1.put_u64(1234); - builder2.put_u64(1234); + span_builder1.put_slice(&1234_u64.to_ne_bytes()); + span_builder2.put_slice(&1234_u64.to_ne_bytes()); - let span1 = builder1.consume(nz!(8)); - let span2 = builder2.consume(nz!(8)); + let span1 = span_builder1.consume(nz!(8)); + let span2 = span_builder2.consume(nz!(8)); - let sequence = BytesView::from_spans(vec![span1, span2]); + let view = BytesView::from_spans(vec![span1, span2]); - sequence.extend_lifetime() + view.extend_lifetime() }; // The sequence was destroyed and all BlockRefs it was holding are gone. @@ -871,236 +843,189 @@ mod tests { } #[test] - fn from_sequences() { - let mut builder = std_alloc_block::allocate(nz!(100)).into_span_builder(); - - builder.put_u64(1234); - builder.put_u64(5678); - - let span1 = builder.consume(nz!(8)); - let span2 = builder.consume(nz!(8)); + fn from_views() { + let mut span_builder = std_alloc_block::allocate(nz!(100)).into_span_builder(); - let sequence1 = BytesView::from_spans(vec![span1]); - let sequence2 = BytesView::from_spans(vec![span2]); + span_builder.put_slice(&1234_u64.to_ne_bytes()); + span_builder.put_slice(&5678_u64.to_ne_bytes()); - let mut combined = BytesView::from_sequences(vec![sequence1, sequence2]); + let span1 = span_builder.consume(nz!(8)); + let span2 = span_builder.consume(nz!(8)); - assert_eq!(16, combined.remaining()); + let view1 = BytesView::from_spans(vec![span1]); + let view2 = BytesView::from_spans(vec![span2]); - assert_eq!(combined.get_u64(), 1234); - assert_eq!(combined.get_u64(), 5678); - } - - #[test] - fn empty_sequence() { - let sequence = BytesView::default(); + let mut combined_view = BytesView::from_views(vec![view1, view2]); - assert!(sequence.is_empty()); - assert_eq!(0, sequence.remaining()); - assert_eq!(0, sequence.chunk().len()); + assert_eq!(16, combined_view.len()); - let bytes = sequence.into_bytes(); - assert_eq!(0, bytes.len()); + assert_eq!(combined_view.get_num_ne::(), 1234); + assert_eq!(combined_view.get_num_ne::(), 5678); } #[test] - fn into_bytes() { - let mut builder = std_alloc_block::allocate(nz!(100)).into_span_builder(); - - builder.put_u64(1234); - builder.put_u64(5678); - - let span1 = builder.consume(nz!(8)); - let span2 = builder.consume(nz!(8)); + fn empty_view() { + let view = BytesView::default(); - let sequence_single_span = BytesView::from_spans(vec![span1.clone()]); - let sequence_multi_span = BytesView::from_spans(vec![span1, span2]); - - let mut bytes = sequence_single_span.clone().into_bytes(); - assert_eq!(8, bytes.len()); - assert_eq!(1234, bytes.get_u64()); - - let mut bytes = sequence_single_span.into_bytes(); - assert_eq!(8, bytes.len()); - assert_eq!(1234, bytes.get_u64()); - - let mut bytes = sequence_multi_span.into_bytes(); - assert_eq!(16, bytes.len()); - assert_eq!(1234, bytes.get_u64()); - assert_eq!(5678, bytes.get_u64()); - } - - #[test] - fn thread_safe_type() { - assert_impl_all!(BytesView: Send, Sync); + assert!(view.is_empty()); + assert_eq!(0, view.len()); + assert_eq!(0, view.first_slice().len()); } #[test] - fn slice_from_single_span_sequence() { - // A very simple sequence to start with, consisting of just one 100 byte span. + fn slice_from_single_span_view() { + // A very simple view to start with, consisting of just one 100 byte span. let span_builder = std_alloc_block::allocate(nz!(100)).into_span_builder(); - let mut sb = BytesBuf::from_span_builders([span_builder]); + let mut buf = BytesBuf::from_span_builders([span_builder]); for i in 0..100 { - #[expect( - clippy::cast_possible_truncation, - clippy::cast_sign_loss, - reason = "manually validated range of values is safe" - )] - sb.put_u8(i as u8); + buf.put_byte(i); } - let sequence = sb.consume_all(); + let view = buf.consume_all(); - let mut sub_sequence = sequence.slice(50..55); + let mut sliced_view = view.range(50..55); - assert_eq!(5, sub_sequence.len()); - assert_eq!(100, sequence.len()); + assert_eq!(5, sliced_view.len()); + assert_eq!(100, view.len()); - assert_eq!(50, sub_sequence.get_u8()); + assert_eq!(50, sliced_view.get_byte()); - assert_eq!(4, sub_sequence.len()); - assert_eq!(100, sequence.len()); + assert_eq!(4, sliced_view.len()); + assert_eq!(100, view.len()); - assert_eq!(51, sub_sequence.get_u8()); - assert_eq!(52, sub_sequence.get_u8()); - assert_eq!(53, sub_sequence.get_u8()); - assert_eq!(54, sub_sequence.get_u8()); + assert_eq!(51, sliced_view.get_byte()); + assert_eq!(52, sliced_view.get_byte()); + assert_eq!(53, sliced_view.get_byte()); + assert_eq!(54, sliced_view.get_byte()); - assert_eq!(0, sub_sequence.len()); + assert_eq!(0, sliced_view.len()); - assert!(sequence.slice_checked(0..101).is_none()); - assert!(sequence.slice_checked(100..101).is_none()); - assert!(sequence.slice_checked(101..101).is_none()); + assert!(view.range_checked(0..101).is_none()); + assert!(view.range_checked(100..101).is_none()); + assert!(view.range_checked(101..101).is_none()); } #[test] - fn slice_from_multi_span_sequence() { + fn slice_from_multi_span_view() { const SPAN_SIZE: NonZero = nz!(10); - // A multi-span sequence, 10 bytes x10. + // A multi-span view, 10 bytes x10. let span_builders = iter::repeat_with(|| std_alloc_block::allocate(SPAN_SIZE).into_span_builder()) .take(10) .collect::>(); - let mut sb = BytesBuf::from_span_builders(span_builders); + let mut buf = BytesBuf::from_span_builders(span_builders); for i in 0..100 { - #[expect( - clippy::cast_possible_truncation, - clippy::cast_sign_loss, - reason = "manually validated range of values is safe" - )] - sb.put_u8(i as u8); + buf.put_byte(i); } - let sequence = sb.consume_all(); + let view = buf.consume_all(); - let mut first5 = sequence.slice(0..5); + let mut first5 = view.range(0..5); assert_eq!(5, first5.len()); - assert_eq!(100, sequence.len()); - assert_eq!(0, first5.get_u8()); + assert_eq!(100, view.len()); + assert_eq!(0, first5.get_byte()); - let mut last5 = sequence.slice(95..100); + let mut last5 = view.range(95..100); assert_eq!(5, last5.len()); - assert_eq!(100, sequence.len()); - assert_eq!(95, last5.get_u8()); + assert_eq!(100, view.len()); + assert_eq!(95, last5.get_byte()); - let mut middle5 = sequence.slice(49..54); + let mut middle5 = view.range(49..54); assert_eq!(5, middle5.len()); - assert_eq!(100, sequence.len()); - assert_eq!(49, middle5.get_u8()); - assert_eq!(50, middle5.get_u8()); - assert_eq!(51, middle5.get_u8()); - assert_eq!(52, middle5.get_u8()); - assert_eq!(53, middle5.get_u8()); - - assert!(sequence.slice_checked(0..101).is_none()); - assert!(sequence.slice_checked(100..101).is_none()); - assert!(sequence.slice_checked(101..101).is_none()); + assert_eq!(100, view.len()); + assert_eq!(49, middle5.get_byte()); + assert_eq!(50, middle5.get_byte()); + assert_eq!(51, middle5.get_byte()); + assert_eq!(52, middle5.get_byte()); + assert_eq!(53, middle5.get_byte()); + + assert!(view.range_checked(0..101).is_none()); + assert!(view.range_checked(100..101).is_none()); + assert!(view.range_checked(101..101).is_none()); } #[test] fn slice_indexing_kinds() { let span_builder = std_alloc_block::allocate(nz!(10)).into_span_builder(); - let mut sb = BytesBuf::from_span_builders([span_builder]); - sb.put_u8(0); - sb.put_u8(1); - sb.put_u8(2); - sb.put_u8(3); - sb.put_u8(4); - sb.put_u8(5); + let mut buf = BytesBuf::from_span_builders([span_builder]); + buf.put_byte(0); + buf.put_byte(1); + buf.put_byte(2); + buf.put_byte(3); + buf.put_byte(4); + buf.put_byte(5); - let sequence = sb.consume_all(); + let sequence = buf.consume_all(); - let mut middle_four = sequence.slice(1..5); + let mut middle_four = sequence.range(1..5); assert_eq!(4, middle_four.len()); - assert_eq!(1, middle_four.get_u8()); - assert_eq!(2, middle_four.get_u8()); - assert_eq!(3, middle_four.get_u8()); - assert_eq!(4, middle_four.get_u8()); + assert_eq!(1, middle_four.get_byte()); + assert_eq!(2, middle_four.get_byte()); + assert_eq!(3, middle_four.get_byte()); + assert_eq!(4, middle_four.get_byte()); - let mut middle_four = sequence.slice(1..=4); + let mut middle_four = sequence.range(1..=4); assert_eq!(4, middle_four.len()); - assert_eq!(1, middle_four.get_u8()); - assert_eq!(2, middle_four.get_u8()); - assert_eq!(3, middle_four.get_u8()); - assert_eq!(4, middle_four.get_u8()); + assert_eq!(1, middle_four.get_byte()); + assert_eq!(2, middle_four.get_byte()); + assert_eq!(3, middle_four.get_byte()); + assert_eq!(4, middle_four.get_byte()); - let mut last_two = sequence.slice(4..); + let mut last_two = sequence.range(4..); assert_eq!(2, last_two.len()); - assert_eq!(4, last_two.get_u8()); - assert_eq!(5, last_two.get_u8()); + assert_eq!(4, last_two.get_byte()); + assert_eq!(5, last_two.get_byte()); - let mut first_two = sequence.slice(..2); + let mut first_two = sequence.range(..2); assert_eq!(2, first_two.len()); - assert_eq!(0, first_two.get_u8()); - assert_eq!(1, first_two.get_u8()); + assert_eq!(0, first_two.get_byte()); + assert_eq!(1, first_two.get_byte()); - let mut first_two = sequence.slice(..=1); + let mut first_two = sequence.range(..=1); assert_eq!(2, first_two.len()); - assert_eq!(0, first_two.get_u8()); - assert_eq!(1, first_two.get_u8()); + assert_eq!(0, first_two.get_byte()); + assert_eq!(1, first_two.get_byte()); } #[test] fn slice_checked_with_excluded_start_bound() { - use std::ops::Bound; - let span_builder = std_alloc_block::allocate(nz!(100)).into_span_builder(); - let mut sb = BytesBuf::from_span_builders([span_builder]); - sb.put_u8(0); - sb.put_u8(1); - sb.put_u8(2); - sb.put_u8(3); - sb.put_u8(4); - sb.put_u8(5); - sb.put_u8(6); - sb.put_u8(7); - sb.put_u8(8); + let mut buf = BytesBuf::from_span_builders([span_builder]); + buf.put_byte(0); + buf.put_byte(1); + buf.put_byte(2); + buf.put_byte(3); + buf.put_byte(4); + buf.put_byte(5); + buf.put_byte(6); + buf.put_byte(7); + buf.put_byte(8); - let sequence = sb.consume_all(); + let view = buf.consume_all(); // Test with excluded start bound: (Bound::Excluded(1), Bound::Excluded(5)) // This should be equivalent to 2..5 (items at indices 2, 3, 4) - let sliced = sequence.slice_checked((Bound::Excluded(1), Bound::Excluded(5))); + let sliced = view.range_checked((Bound::Excluded(1), Bound::Excluded(5))); assert!(sliced.is_some()); let mut sliced = sliced.unwrap(); assert_eq!(3, sliced.len()); - assert_eq!(2, sliced.get_u8()); - assert_eq!(3, sliced.get_u8()); - assert_eq!(4, sliced.get_u8()); + assert_eq!(2, sliced.get_byte()); + assert_eq!(3, sliced.get_byte()); + assert_eq!(4, sliced.get_byte()); // Test edge case: excluded start at the last valid index returns empty sequence - let sliced = sequence.slice_checked((Bound::Excluded(8), Bound::Unbounded)); + let sliced = view.range_checked((Bound::Excluded(8), Bound::Unbounded)); assert!(sliced.is_some()); assert_eq!(0, sliced.unwrap().len()); // Test edge case: excluded start that would overflow when adding 1 - let sliced = sequence.slice_checked((Bound::Excluded(usize::MAX), Bound::Unbounded)); + let sliced = view.range_checked((Bound::Excluded(usize::MAX), Bound::Unbounded)); assert!(sliced.is_none()); } @@ -1108,69 +1033,64 @@ mod tests { fn slice_oob_is_panic() { let span_builder = std_alloc_block::allocate(nz!(1000)).into_span_builder(); - let mut sb = BytesBuf::from_span_builders([span_builder]); - sb.put_bytes(0, 100); + let mut buf = BytesBuf::from_span_builders([span_builder]); + buf.put_byte_repeated(0, 100); - let sequence = sb.consume_all(); + let view = buf.consume_all(); - assert_panic!(_ = sequence.slice(0..101)); - assert_panic!(_ = sequence.slice(0..=100)); - assert_panic!(_ = sequence.slice(100..=100)); - assert_panic!(_ = sequence.slice(100..101)); - assert_panic!(_ = sequence.slice(101..)); - assert_panic!(_ = sequence.slice(101..101)); - assert_panic!(_ = sequence.slice(101..=101)); + assert_panic!(_ = view.range(0..101)); + assert_panic!(_ = view.range(0..=100)); + assert_panic!(_ = view.range(100..=100)); + assert_panic!(_ = view.range(100..101)); + assert_panic!(_ = view.range(101..)); + assert_panic!(_ = view.range(101..101)); + assert_panic!(_ = view.range(101..=101)); } #[test] fn slice_at_boundary_is_not_panic() { let span_builder = std_alloc_block::allocate(nz!(100)).into_span_builder(); - let mut sb = BytesBuf::from_span_builders([span_builder]); - sb.put_bytes(0, 100); + let mut buf = BytesBuf::from_span_builders([span_builder]); + buf.put_byte_repeated(0, 100); - let sequence = sb.consume_all(); + let view = buf.consume_all(); - assert_eq!(0, sequence.slice(0..0).len()); - assert_eq!(1, sequence.slice(0..=0).len()); - assert_eq!(0, sequence.slice(..0).len()); - assert_eq!(1, sequence.slice(..=0).len()); - assert_eq!(0, sequence.slice(100..100).len()); - assert_eq!(0, sequence.slice(99..99).len()); - assert_eq!(1, sequence.slice(99..=99).len()); - assert_eq!(1, sequence.slice(99..).len()); - assert_eq!(100, sequence.slice(..).len()); + assert_eq!(0, view.range(0..0).len()); + assert_eq!(1, view.range(0..=0).len()); + assert_eq!(0, view.range(..0).len()); + assert_eq!(1, view.range(..=0).len()); + assert_eq!(0, view.range(100..100).len()); + assert_eq!(0, view.range(99..99).len()); + assert_eq!(1, view.range(99..=99).len()); + assert_eq!(1, view.range(99..).len()); + assert_eq!(100, view.range(..).len()); } #[test] fn slice_empty_is_empty_if_not_oob() { let span_builder = std_alloc_block::allocate(nz!(100)).into_span_builder(); - let mut sb = BytesBuf::from_span_builders([span_builder]); + let mut buf = BytesBuf::from_span_builders([span_builder]); for i in 0..100 { - #[expect( - clippy::cast_possible_truncation, - clippy::cast_sign_loss, - reason = "manually validated range of values is safe" - )] - sb.put_u8(i as u8); + buf.put_byte(i); } - let sequence = sb.consume_all(); + let view = buf.consume_all(); - let sub_sequence = sequence.slice(50..50); + let sub_sequence = view.range(50..50); assert_eq!(0, sub_sequence.len()); - // 100 is the index at the end of the sequence - still in-bounds, if at edge. - let sub_sequence = sequence.slice(100..100); + // 100 is the index at the end of the view - still in-bounds, if at edge. + let sub_sequence = view.range(100..100); assert_eq!(0, sub_sequence.len()); - assert!(sequence.slice_checked(101..101).is_none()); + assert!(view.range_checked(101..101).is_none()); } #[test] - fn consume_all_chunks() { + fn consume_all_slices() { const SPAN_SIZE: NonZero = nz!(10); // A multi-span sequence, 10 bytes x10. @@ -1178,57 +1098,52 @@ mod tests { .take(10) .collect::>(); - let mut sb = BytesBuf::from_span_builders(span_builders); + let mut buf = BytesBuf::from_span_builders(span_builders); for i in 0..100 { - #[expect( - clippy::cast_possible_truncation, - clippy::cast_sign_loss, - reason = "manually validated range of values is safe" - )] - sb.put_u8(i as u8); + buf.put_byte(i); } - let mut sequence = sb.consume_all(); + let mut view = buf.consume_all(); - let mut chunk_index = 0; + let mut slice_index = 0; let mut bytes_consumed = 0; - sequence.consume_all_chunks(|chunk| { - assert_eq!(chunk.len(), 10); - bytes_consumed += chunk.len(); + view.consume_all_slices(|slice| { + assert_eq!(slice.len(), 10); + bytes_consumed += slice.len(); for i in 0..10 { - assert_eq!(chunk_index * 10 + i, chunk[i] as usize); + assert_eq!(slice_index * 10 + i, slice[i] as usize); } - chunk_index += 1; + slice_index += 1; }); assert_eq!(bytes_consumed, 100); - sequence.consume_all_chunks(|_| unreachable!("sequence should now be empty")); + view.consume_all_slices(|_| unreachable!("view should now be empty")); } #[test] fn multithreaded_usage() { - fn post_to_another_thread(s: BytesView) { + fn post_to_another_thread(view: BytesView) { thread::spawn(move || { - let mut s = s; - assert_eq!(s.get_u8(), b'H'); - assert_eq!(s.get_u8(), b'e'); - assert_eq!(s.get_u8(), b'l'); - assert_eq!(s.get_u8(), b'l'); - assert_eq!(s.get_u8(), b'o'); + let mut view = view; + assert_eq!(view.get_byte(), b'H'); + assert_eq!(view.get_byte(), b'e'); + assert_eq!(view.get_byte(), b'l'); + assert_eq!(view.get_byte(), b'l'); + assert_eq!(view.get_byte(), b'o'); }) .join() .unwrap(); } let memory = TransparentTestMemory::new(); - let s = BytesView::copied_from_slice(b"Hello, world!", &memory); + let view = BytesView::copied_from_slice(b"Hello, world!", &memory); - post_to_another_thread(s); + post_to_another_thread(view); } #[test] @@ -1237,14 +1152,14 @@ mod tests { let segment1 = BytesView::copied_from_slice(b"Hello, world!", &memory); let segment2 = BytesView::copied_from_slice(b"Hello, another world!", &memory); - let sequence = BytesView::from_sequences(vec![segment1.clone(), segment2.clone()]); + let view = BytesView::from_views(vec![segment1.clone(), segment2.clone()]); let mut io_slices = vec![]; - let ioslice_count = Buf::chunks_vectored(&sequence, &mut io_slices); + let ioslice_count = view.io_slices(&mut io_slices); assert_eq!(ioslice_count, 0); let mut io_slices = vec![IoSlice::new(&[]); 4]; - let ioslice_count = Buf::chunks_vectored(&sequence, &mut io_slices); + let ioslice_count = view.io_slices(&mut io_slices); assert_eq!(ioslice_count, 2); assert_eq!(io_slices[0].len(), segment1.len()); @@ -1257,14 +1172,14 @@ mod tests { let segment1 = BytesView::copied_from_slice(b"Hello, world!", &memory); let segment2 = BytesView::copied_from_slice(b"Hello, another world!", &memory); - let sequence = BytesView::from_sequences(vec![segment1.clone(), segment2.clone()]); + let view = BytesView::from_views(vec![segment1.clone(), segment2.clone()]); let mut slices: Vec<&[u8]> = vec![]; - let slice_count = sequence.chunks_as_slices_vectored(&mut slices); + let slice_count = view.slices(&mut slices); assert_eq!(slice_count, 0); let mut slices: Vec<&[u8]> = vec![&[]; 4]; - let slice_count = sequence.chunks_as_slices_vectored(&mut slices); + let slice_count = view.slices(&mut slices); assert_eq!(slice_count, 2); assert_eq!(slices[0].len(), segment1.len()); @@ -1275,100 +1190,100 @@ mod tests { fn eq_sequence() { let memory = TransparentTestMemory::new(); - let s1 = BytesView::copied_from_slice(b"Hello, world!", &memory); - let s2 = BytesView::copied_from_slice(b"Hello, world!", &memory); + let view1 = BytesView::copied_from_slice(b"Hello, world!", &memory); + let view2 = BytesView::copied_from_slice(b"Hello, world!", &memory); - assert_eq!(s1, s2); + assert_eq!(view1, view2); - let s3 = BytesView::copied_from_slice(b"Jello, world!", &memory); + let view3 = BytesView::copied_from_slice(b"Jello, world!", &memory); - assert_ne!(s1, s3); + assert_ne!(view1, view3); - let s4 = BytesView::copied_from_slice(b"Hello, world! ", &memory); + let view4 = BytesView::copied_from_slice(b"Hello, world! ", &memory); - assert_ne!(s1, s4); + assert_ne!(view1, view4); - let s5_part1 = BytesView::copied_from_slice(b"Hello, ", &memory); - let s5_part2 = BytesView::copied_from_slice(b"world!", &memory); - let s5 = BytesView::from_sequences([s5_part1, s5_part2]); + let view5_part1 = BytesView::copied_from_slice(b"Hello, ", &memory); + let view5_part2 = BytesView::copied_from_slice(b"world!", &memory); + let view5 = BytesView::from_views([view5_part1, view5_part2]); - assert_eq!(s1, s5); - assert_ne!(s5, s3); + assert_eq!(view1, view5); + assert_ne!(view5, view3); - let s6 = BytesView::copied_from_slice(b"Hello, ", &memory); + let view6 = BytesView::copied_from_slice(b"Hello, ", &memory); - assert_ne!(s1, s6); - assert_ne!(s5, s6); + assert_ne!(view1, view6); + assert_ne!(view5, view6); } #[test] fn eq_slice() { let memory = TransparentTestMemory::new(); - let s1 = BytesView::copied_from_slice(b"Hello, world!", &memory); + let view1 = BytesView::copied_from_slice(b"Hello, world!", &memory); - assert_eq!(s1, b"Hello, world!".as_slice()); - assert_ne!(s1, b"Jello, world!".as_slice()); - assert_ne!(s1, b"Hello, world! ".as_slice()); + assert_eq!(view1, b"Hello, world!".as_slice()); + assert_ne!(view1, b"Jello, world!".as_slice()); + assert_ne!(view1, b"Hello, world! ".as_slice()); - assert_eq!(b"Hello, world!".as_slice(), s1); - assert_ne!(b"Jello, world!".as_slice(), s1); - assert_ne!(b"Hello, world! ".as_slice(), s1); + assert_eq!(b"Hello, world!".as_slice(), view1); + assert_ne!(b"Jello, world!".as_slice(), view1); + assert_ne!(b"Hello, world! ".as_slice(), view1); - let s2_part1 = BytesView::copied_from_slice(b"Hello, ", &memory); - let s2_part2 = BytesView::copied_from_slice(b"world!", &memory); - let s2 = BytesView::from_sequences([s2_part1, s2_part2]); + let view2_part1 = BytesView::copied_from_slice(b"Hello, ", &memory); + let view2_part2 = BytesView::copied_from_slice(b"world!", &memory); + let view2 = BytesView::from_views([view2_part1, view2_part2]); - assert_eq!(s2, b"Hello, world!".as_slice()); - assert_ne!(s2, b"Jello, world!".as_slice()); - assert_ne!(s2, b"Hello, world! ".as_slice()); - assert_ne!(s2, b"Hello, ".as_slice()); + assert_eq!(view2, b"Hello, world!".as_slice()); + assert_ne!(view2, b"Jello, world!".as_slice()); + assert_ne!(view2, b"Hello, world! ".as_slice()); + assert_ne!(view2, b"Hello, ".as_slice()); - assert_eq!(b"Hello, world!".as_slice(), s2); - assert_ne!(b"Jello, world!".as_slice(), s2); - assert_ne!(b"Hello, world! ".as_slice(), s2); - assert_ne!(b"Hello, ".as_slice(), s2); + assert_eq!(b"Hello, world!".as_slice(), view2); + assert_ne!(b"Jello, world!".as_slice(), view2); + assert_ne!(b"Hello, world! ".as_slice(), view2); + assert_ne!(b"Hello, ".as_slice(), view2); } #[test] fn eq_array() { let memory = TransparentTestMemory::new(); - let s1 = BytesView::copied_from_slice(b"Hello, world!", &memory); + let view1 = BytesView::copied_from_slice(b"Hello, world!", &memory); - assert_eq!(s1, b"Hello, world!"); - assert_ne!(s1, b"Jello, world!"); - assert_ne!(s1, b"Hello, world! "); + assert_eq!(view1, b"Hello, world!"); + assert_ne!(view1, b"Jello, world!"); + assert_ne!(view1, b"Hello, world! "); - assert_eq!(b"Hello, world!", s1); - assert_ne!(b"Jello, world!", s1); - assert_ne!(b"Hello, world! ", s1); + assert_eq!(b"Hello, world!", view1); + assert_ne!(b"Jello, world!", view1); + assert_ne!(b"Hello, world! ", view1); - let s2_part1 = BytesView::copied_from_slice(b"Hello, ", &memory); - let s2_part2 = BytesView::copied_from_slice(b"world!", &memory); - let s2 = BytesView::from_sequences([s2_part1, s2_part2]); + let view2_part1 = BytesView::copied_from_slice(b"Hello, ", &memory); + let view2_part2 = BytesView::copied_from_slice(b"world!", &memory); + let view2 = BytesView::from_views([view2_part1, view2_part2]); - assert_eq!(s2, b"Hello, world!"); - assert_ne!(s2, b"Jello, world!"); - assert_ne!(s2, b"Hello, world! "); - assert_ne!(s2, b"Hello, "); + assert_eq!(view2, b"Hello, world!"); + assert_ne!(view2, b"Jello, world!"); + assert_ne!(view2, b"Hello, world! "); + assert_ne!(view2, b"Hello, "); - assert_eq!(b"Hello, world!", s2); - assert_ne!(b"Jello, world!", s2); - assert_ne!(b"Hello, world! ", s2); - assert_ne!(b"Hello, ", s2); + assert_eq!(b"Hello, world!", view2); + assert_ne!(b"Jello, world!", view2); + assert_ne!(b"Hello, world! ", view2); + assert_ne!(b"Hello, ", view2); } #[test] fn meta_none() { let memory = TransparentTestMemory::new(); - let s1 = BytesView::copied_from_slice(b"Hello, ", &memory); - let s2 = BytesView::copied_from_slice(b"world!", &memory); + let view1 = BytesView::copied_from_slice(b"Hello, ", &memory); + let view2 = BytesView::copied_from_slice(b"world!", &memory); - let s = BytesView::from_sequences([s1, s2]); + let view = BytesView::from_views([view1, view2]); - let mut metas_iter = s.iter_chunk_metas(); + let mut metas_iter = view.iter_slice_metas(); // We have two chunks, both without metadata. assert!(matches!(metas_iter.next(), Some(None))); @@ -1396,14 +1311,14 @@ mod tests { // SAFETY: We guarantee exclusive access to the memory capacity. let block2 = unsafe { block2.as_ref().to_block() }; - let mut builder = BytesBuf::from_blocks([block1, block2]); + let mut buf = BytesBuf::from_blocks([block1, block2]); // Add enough bytes to make use of both blocks. - builder.put_bytes(123, 166); + buf.put_byte_repeated(123, 166); - let s = builder.consume_all(); + let view = buf.consume_all(); - let mut metas_iter = s.iter_chunk_metas(); + let mut metas_iter = view.iter_slice_metas(); // NB! There is no requirement that the BytesBuf use the blocks in the order we gave // them in. We use white-box knowledge here to know that it actually reverses the order. @@ -1424,126 +1339,126 @@ mod tests { fn append_single_span() { let memory = TransparentTestMemory::new(); - // Create two single-span sequences - let mut s1 = BytesView::copied_from_slice(b"Hello, ", &memory); - let s2 = BytesView::copied_from_slice(b"world!", &memory); + // Create two single-span views. + let mut view1 = BytesView::copied_from_slice(b"Hello, ", &memory); + let view2 = BytesView::copied_from_slice(b"world!", &memory); - assert_eq!(s1.len(), 7); - assert_eq!(s2.len(), 6); + assert_eq!(view1.len(), 7); + assert_eq!(view2.len(), 6); - s1.append(s2); + view1.append(view2); - assert_eq!(s1.len(), 13); - assert_eq!(s1, b"Hello, world!"); + assert_eq!(view1.len(), 13); + assert_eq!(view1, b"Hello, world!"); } #[test] fn append_multi_span() { let memory = TransparentTestMemory::new(); - // Create two multi-span sequences (2 spans each) - let s1_part1 = BytesView::copied_from_slice(b"AAA", &memory); - let s1_part2 = BytesView::copied_from_slice(b"BBB", &memory); - let mut s1 = BytesView::from_sequences([s1_part1, s1_part2]); + // Create two multi-span views (2 spans each) + let view1_part1 = BytesView::copied_from_slice(b"AAA", &memory); + let view1_part2 = BytesView::copied_from_slice(b"BBB", &memory); + let mut view1 = BytesView::from_views([view1_part1, view1_part2]); - let s2_part1 = BytesView::copied_from_slice(b"CCC", &memory); - let s2_part2 = BytesView::copied_from_slice(b"DDD", &memory); - let s2 = BytesView::from_sequences([s2_part1, s2_part2]); + let view2_part1 = BytesView::copied_from_slice(b"CCC", &memory); + let view2_part2 = BytesView::copied_from_slice(b"DDD", &memory); + let view2 = BytesView::from_views([view2_part1, view2_part2]); - assert_eq!(s1.len(), 6); - assert_eq!(s2.len(), 6); + assert_eq!(view1.len(), 6); + assert_eq!(view2.len(), 6); - s1.append(s2); + view1.append(view2); - assert_eq!(s1.len(), 12); - assert_eq!(s1, b"AAABBBCCCDDD"); + assert_eq!(view1.len(), 12); + assert_eq!(view1, b"AAABBBCCCDDD"); } #[test] - fn append_empty_sequences() { + fn append_empty_view() { let memory = TransparentTestMemory::new(); - let mut s1 = BytesView::copied_from_slice(b"Hello", &memory); - let s2 = BytesView::new(); + let mut view1 = BytesView::copied_from_slice(b"Hello", &memory); + let view2 = BytesView::new(); - s1.append(s2); - assert_eq!(s1.len(), 5); - assert_eq!(s1, b"Hello"); + view1.append(view2); + assert_eq!(view1.len(), 5); + assert_eq!(view1, b"Hello"); - let mut s3 = BytesView::new(); - let s4 = BytesView::copied_from_slice(b"world", &memory); + let mut view3 = BytesView::new(); + let view4 = BytesView::copied_from_slice(b"world", &memory); - s3.append(s4); - assert_eq!(s3.len(), 5); - assert_eq!(s3, b"world"); + view3.append(view4); + assert_eq!(view3.len(), 5); + assert_eq!(view3, b"world"); } #[test] fn concat_single_span() { let memory = TransparentTestMemory::new(); - // Create two single-span sequences - let s1 = BytesView::copied_from_slice(b"Hello, ", &memory); - let s2 = BytesView::copied_from_slice(b"world!", &memory); + // Create two single-span views + let view1 = BytesView::copied_from_slice(b"Hello, ", &memory); + let view2 = BytesView::copied_from_slice(b"world!", &memory); - assert_eq!(s1.len(), 7); - assert_eq!(s2.len(), 6); + assert_eq!(view1.len(), 7); + assert_eq!(view2.len(), 6); - let s3 = s1.concat(s2); + let view3 = view1.concat(view2); - // Original sequences unchanged - assert_eq!(s1.len(), 7); - assert_eq!(s1, b"Hello, "); + // Original view unchanged + assert_eq!(view1.len(), 7); + assert_eq!(view1, b"Hello, "); - // New sequence contains combined data - assert_eq!(s3.len(), 13); - assert_eq!(s3, b"Hello, world!"); + // New view contains combined data + assert_eq!(view3.len(), 13); + assert_eq!(view3, b"Hello, world!"); } #[test] fn concat_multi_span() { let memory = TransparentTestMemory::new(); - // Create two multi-span sequences (2 spans each) - let s1_part1 = BytesView::copied_from_slice(b"AAA", &memory); - let s1_part2 = BytesView::copied_from_slice(b"BBB", &memory); - let s1 = BytesView::from_sequences([s1_part1, s1_part2]); + // Create two multi-span views (2 spans each) + let view1_part1 = BytesView::copied_from_slice(b"AAA", &memory); + let view1_part2 = BytesView::copied_from_slice(b"BBB", &memory); + let view1 = BytesView::from_views([view1_part1, view1_part2]); - let s2_part1 = BytesView::copied_from_slice(b"CCC", &memory); - let s2_part2 = BytesView::copied_from_slice(b"DDD", &memory); - let s2 = BytesView::from_sequences([s2_part1, s2_part2]); + let view2_part1 = BytesView::copied_from_slice(b"CCC", &memory); + let view2_part2 = BytesView::copied_from_slice(b"DDD", &memory); + let view2 = BytesView::from_views([view2_part1, view2_part2]); - assert_eq!(s1.len(), 6); - assert_eq!(s2.len(), 6); + assert_eq!(view1.len(), 6); + assert_eq!(view2.len(), 6); - let s3 = s1.concat(s2); + let view3 = view1.concat(view2); - // Original sequences unchanged - assert_eq!(s1.len(), 6); - assert_eq!(s1, b"AAABBB"); + // Original view unchanged + assert_eq!(view1.len(), 6); + assert_eq!(view1, b"AAABBB"); - // New sequence contains combined data - assert_eq!(s3.len(), 12); - assert_eq!(s3, b"AAABBBCCCDDD"); + // New view contains combined data + assert_eq!(view3.len(), 12); + assert_eq!(view3, b"AAABBBCCCDDD"); } #[test] - fn concat_empty_sequences() { + fn concat_empty_views() { let memory = TransparentTestMemory::new(); - let s1 = BytesView::copied_from_slice(b"Hello", &memory); - let s2 = BytesView::new(); + let view1 = BytesView::copied_from_slice(b"Hello", &memory); + let view2 = BytesView::new(); - let s3 = s1.concat(s2); - assert_eq!(s3.len(), 5); - assert_eq!(s3, b"Hello"); + let view3 = view1.concat(view2); + assert_eq!(view3.len(), 5); + assert_eq!(view3, b"Hello"); - let s4 = BytesView::new(); - let s5 = BytesView::copied_from_slice(b"world", &memory); + let view4 = BytesView::new(); + let view5 = BytesView::copied_from_slice(b"world", &memory); - let s6 = s4.concat(s5); - assert_eq!(s6.len(), 5); - assert_eq!(s6, b"world"); + let view6 = view4.concat(view5); + assert_eq!(view6.len(), 5); + assert_eq!(view6, b"world"); } #[test] diff --git a/crates/bytesbuf/src/view_get.rs b/crates/bytesbuf/src/view_get.rs new file mode 100644 index 00000000..c86ee36e --- /dev/null +++ b/crates/bytesbuf/src/view_get.rs @@ -0,0 +1,515 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +//! We separate out all the consumption methods for ease of maintenance. + +use std::mem::MaybeUninit; +use std::ptr; + +use num_traits::FromBytes; + +use crate::BytesView; + +impl BytesView { + /// Consumes a `u8` from the byte sequence. + /// + /// The byte is removed from the front of the view, shrinking it. + /// + /// # Panics + /// + /// Panics if the view does not cover enough bytes of data. + #[inline] + #[must_use] + pub fn get_byte(&mut self) -> u8 { + let byte = *self.first_slice().first().expect("view must cover at least one byte"); + self.advance(1); + byte + } + + /// Transfers bytes into an initialized slice. + /// + /// The bytes are removed from the front of the view, shrinking it. + /// + /// # Panics + /// + /// Panics if the destination is larger than the view. + pub fn copy_to_slice(&mut self, mut dst: &mut [u8]) { + assert!(self.len() >= dst.len()); + + while !dst.is_empty() { + let src = self.first_slice(); + let bytes_to_copy = dst.len().min(src.len()); + + dst[..bytes_to_copy].copy_from_slice(&src[..bytes_to_copy]); + dst = &mut dst[bytes_to_copy..]; + + self.advance(bytes_to_copy); + } + } + + /// Transfers bytes into a potentially uninitialized slice. + /// + /// The bytes are removed from the front of the view, shrinking it. + /// + /// # Panics + /// + /// Panics if the destination is larger than the view. + pub fn copy_to_uninit_slice(&mut self, mut dst: &mut [MaybeUninit]) { + assert!(self.len() >= dst.len()); + + while !dst.is_empty() { + let src = self.first_slice(); + let bytes_to_copy = dst.len().min(src.len()); + + // SAFETY: Both are byte slices, so no alignment concerns. + // We guard against length overflow via min() to constrain to slice length. + unsafe { + ptr::copy_nonoverlapping(src.as_ptr(), dst.as_mut_ptr().cast(), bytes_to_copy); + } + + dst = &mut dst[bytes_to_copy..]; + + self.advance(bytes_to_copy); + } + } + + /// Consumes a number of type `T` in little-endian representation. + /// + /// The bytes of the `T` are removed from the front of the view, shrinking it. + /// + /// # Panics + /// + /// Panics if the view does not cover enough bytes of data. + #[inline] + #[must_use] + pub fn get_num_le(&mut self) -> T + where + T::Bytes: Sized, + { + let size = size_of::(); + assert!(self.len() >= size); + + if let Some(bytes) = self.first_slice().get(..size) { + let bytes_array_ptr = bytes.as_ptr().cast::(); + + // SAFETY: The block is only entered if there are enough bytes in the first slice. + // The target type is an array of bytes, so has no alignment requirements. + let bytes_array_maybe = unsafe { bytes_array_ptr.as_ref() }; + // SAFETY: This is never a null pointer because it came from a reference. + let bytes_array = unsafe { bytes_array_maybe.unwrap_unchecked() }; + + let result = T::from_le_bytes(bytes_array); + self.advance(size); + return result; + } + + // If we got here, there were not enough bytes in the first slice, so we need + // to go collect bytes into an intermediate buffer and deserialize it from there. + // SAFETY: We guarantee the view covers enough bytes - we checked it above. + unsafe { self.get_num_le_buffered() } + } + + /// # Safety + /// + /// The caller is responsible for ensuring that the view covers enough bytes. + /// We do not duplicate length checking as this method is only assumed to be + /// called as a fallback when non-buffered reading proved impossible. + #[cold] // Most reads will not straddle a slice boundary and not require buffering. + unsafe fn get_num_le_buffered(&mut self) -> T + where + T::Bytes: Sized, + { + let mut buffer: MaybeUninit = MaybeUninit::uninit(); + let mut buffer_cursor = buffer.as_mut_ptr().cast::(); + + let mut bytes_remaining = size_of::(); + + while bytes_remaining > 0 { + let first_slice = self.first_slice(); + let bytes_to_copy = bytes_remaining.min(first_slice.len()); + + // SAFETY: The caller has guaranteed that the view covers enough bytes. + // We only copy up to bytes_remaining, which is at most size_of::(), + // so we will not overflow the buffer. + // Both sides are byte arrays/slices so there are no alignment concerns. + unsafe { + ptr::copy_nonoverlapping(first_slice.as_ptr(), buffer_cursor, bytes_to_copy); + } + + // This cannot overflow because we it is guarded by min() above. + bytes_remaining = bytes_remaining.wrapping_sub(bytes_to_copy); + + // SAFETY: We are advancing the cursor in-bounds of the buffer. + buffer_cursor = unsafe { buffer_cursor.add(bytes_to_copy) }; + + self.advance(bytes_to_copy); + } + + // SAFETY: We have filled the buffer with data, initializing it fully. + T::from_le_bytes(&unsafe { buffer.assume_init() }) + } + + /// Consumes a number of type `T` in big-endian representation. + /// + /// The bytes of the `T` are removed from the front of the view, shrinking it. + /// + /// # Panics + /// + /// Panics if the view does not cover enough bytes of data. + #[inline] + #[must_use] + pub fn get_num_be(&mut self) -> T + where + T::Bytes: Sized, + { + let size = size_of::(); + assert!(self.len() >= size); + + if let Some(bytes) = self.first_slice().get(..size) { + let bytes_array_ptr = bytes.as_ptr().cast::(); + + // SAFETY: The block is only entered if there are enough bytes in the first slice. + // The target type is an array of bytes, so has no alignment requirements. + let bytes_array_maybe = unsafe { bytes_array_ptr.as_ref() }; + // SAFETY: This is never a null pointer because it came from a reference. + let bytes_array = unsafe { bytes_array_maybe.unwrap_unchecked() }; + + let result = T::from_be_bytes(bytes_array); + self.advance(size); + return result; + } + + // If we got here, there were not enough bytes in the first slice, so we need + // to go collect bytes into an intermediate buffer and deserialize it from there. + // SAFETY: We guarantee the view covers enough bytes - we checked it above. + unsafe { self.get_num_be_buffered() } + } + + /// # Safety + /// + /// The caller is responsible for ensuring that the view covers enough bytes. + /// We do not duplicate length checking as this method is only assumed to be + /// called as a fallback when non-buffered reading proved impossible. + #[cold] // Most reads will not straddle a slice boundary and not require buffering. + unsafe fn get_num_be_buffered(&mut self) -> T + where + T::Bytes: Sized, + { + let mut buffer: MaybeUninit = MaybeUninit::uninit(); + let mut buffer_cursor = buffer.as_mut_ptr().cast::(); + + let mut bytes_remaining = size_of::(); + + while bytes_remaining > 0 { + let first_slice = self.first_slice(); + let bytes_to_copy = bytes_remaining.min(first_slice.len()); + + // SAFETY: The caller has guaranteed that the view covers enough bytes. + // We only copy up to bytes_remaining, which is at most size_of::(), + // so we will not overflow the buffer. + // Both sides are byte arrays/slices so there are no alignment concerns. + unsafe { + ptr::copy_nonoverlapping(first_slice.as_ptr(), buffer_cursor, bytes_to_copy); + } + + // This cannot overflow because we it is guarded by min() above. + bytes_remaining = bytes_remaining.wrapping_sub(bytes_to_copy); + + // SAFETY: We are advancing the cursor in-bounds of the buffer. + buffer_cursor = unsafe { buffer_cursor.add(bytes_to_copy) }; + + self.advance(bytes_to_copy); + } + + // SAFETY: We have filled the buffer with data, initializing it fully. + T::from_be_bytes(&unsafe { buffer.assume_init() }) + } + + /// Consumes a number of type `T` in native-endian representation. + /// + /// The bytes of the `T` are removed from the front of the view, shrinking it. + /// + /// # Panics + /// + /// Panics if the view does not cover enough bytes of data. + #[inline] + #[must_use] + pub fn get_num_ne(&mut self) -> T + where + T::Bytes: Sized, + { + let size = size_of::(); + assert!(self.len() >= size); + + if let Some(bytes) = self.first_slice().get(..size) { + let bytes_array_ptr = bytes.as_ptr().cast::(); + + // SAFETY: The block is only entered if there are enough bytes in the first slice. + // The target type is an array of bytes, so has no alignment requirements. + let bytes_array_maybe = unsafe { bytes_array_ptr.as_ref() }; + // SAFETY: This is never a null pointer because it came from a reference. + let bytes_array = unsafe { bytes_array_maybe.unwrap_unchecked() }; + + let result = T::from_ne_bytes(bytes_array); + self.advance(size); + return result; + } + + // If we got here, there were not enough bytes in the first slice, so we need + // to go collect bytes into an intermediate buffer and deserialize it from there. + // SAFETY: We guarantee the view covers enough bytes - we checked it above. + unsafe { self.get_num_ne_buffered() } + } + + /// # Safety + /// + /// The caller is responsible for ensuring that the view covers enough bytes. + /// We do not duplicate length checking as this method is only assumed to be + /// called as a fallback when non-buffered reading proved impossible. + #[cold] // Most reads will not straddle a slice boundary and not require buffering. + unsafe fn get_num_ne_buffered(&mut self) -> T + where + T::Bytes: Sized, + { + let mut buffer: MaybeUninit = MaybeUninit::uninit(); + let mut buffer_cursor = buffer.as_mut_ptr().cast::(); + + let mut bytes_remaining = size_of::(); + + while bytes_remaining > 0 { + let first_slice = self.first_slice(); + let bytes_to_copy = bytes_remaining.min(first_slice.len()); + + // SAFETY: The caller has guaranteed that the view covers enough bytes. + // We only copy up to bytes_remaining, which is at most size_of::(), + // so we will not overflow the buffer. + // Both sides are byte arrays/slices so there are no alignment concerns. + unsafe { + ptr::copy_nonoverlapping(first_slice.as_ptr(), buffer_cursor, bytes_to_copy); + } + + // This cannot overflow because we it is guarded by min() above. + bytes_remaining = bytes_remaining.wrapping_sub(bytes_to_copy); + + // SAFETY: We are advancing the cursor in-bounds of the buffer. + buffer_cursor = unsafe { buffer_cursor.add(bytes_to_copy) }; + + self.advance(bytes_to_copy); + } + + // SAFETY: We have filled the buffer with data, initializing it fully. + T::from_ne_bytes(&unsafe { buffer.assume_init() }) + } +} + +#[cfg_attr(coverage_nightly, coverage(off))] +#[cfg(test)] +mod tests { + use super::*; + use crate::TransparentTestMemory; + + #[test] + fn get_byte() { + let memory = TransparentTestMemory::new(); + let mut view = BytesView::copied_from_slice(&[1, 2, 3, 4], &memory); + + assert_eq!(view.get_byte(), 1); + assert_eq!(view.get_byte(), 2); + assert_eq!(view.get_byte(), 3); + assert_eq!(view.get_byte(), 4); + assert!(view.is_empty()); + } + + #[test] + fn copy_to_slice() { + let memory = TransparentTestMemory::new(); + let mut view = BytesView::copied_from_slice(&[1, 2, 3, 4], &memory); + + let mut dst = [0u8; 4]; + view.copy_to_slice(&mut dst); + + assert_eq!(dst, [1, 2, 3, 4]); + assert!(view.is_empty()); + } + + #[test] + fn copy_to_smaller_slice_copies_partially() { + let memory = TransparentTestMemory::new(); + let mut view = BytesView::copied_from_slice(&[1, 2, 3, 4], &memory); + + let mut dst = [0u8; 3]; + view.copy_to_slice(&mut dst); + + assert_eq!(dst, [1, 2, 3]); + assert_eq!(view.len(), 1); + } + + #[test] + #[should_panic] + fn copy_to_bigger_slice_panics() { + let memory = TransparentTestMemory::new(); + let mut view = BytesView::copied_from_slice(&[1, 2, 3, 4], &memory); + + let mut dst = [0u8; 8]; + view.copy_to_slice(&mut dst); + } + + #[test] + fn copy_to_uninit_slice() { + let memory = TransparentTestMemory::new(); + let mut view = BytesView::copied_from_slice(&[1, 2, 3, 4], &memory); + + let mut dst = [MaybeUninit::::uninit(); 4]; + view.copy_to_uninit_slice(&mut dst); + + // SAFETY: It has now been initialized. + let dst = unsafe { std::mem::transmute::<[MaybeUninit; 4], [u8; 4]>(dst) }; + + assert_eq!(dst, [1, 2, 3, 4]); + assert!(view.is_empty()); + } + + #[test] + fn copy_to_uninit_smaller_slice_copies_partially() { + let memory = TransparentTestMemory::new(); + let mut view = BytesView::copied_from_slice(&[1, 2, 3, 4], &memory); + + let mut dst = [MaybeUninit::::uninit(); 3]; + view.copy_to_uninit_slice(&mut dst); + + // SAFETY: It has now been initialized. + let dst = unsafe { std::mem::transmute::<[MaybeUninit; 3], [u8; 3]>(dst) }; + + assert_eq!(dst, [1, 2, 3]); + assert_eq!(view.len(), 1); + } + + #[test] + #[should_panic] + fn copy_to_uninit_bigger_slice_panics() { + let memory = TransparentTestMemory::new(); + let mut view = BytesView::copied_from_slice(&[1, 2, 3, 4], &memory); + + let mut dst = [MaybeUninit::::uninit(); 8]; + view.copy_to_uninit_slice(&mut dst); + } + + #[test] + fn copy_to_slice_multi_span() { + let memory = TransparentTestMemory::new(); + let data_part1 = [10_u8, 20]; + let data_part2 = [30_u8, 40, 50]; + let view_part1 = BytesView::copied_from_slice(&data_part1, &memory); + let view_part2 = BytesView::copied_from_slice(&data_part2, &memory); + let mut view_combined = BytesView::from_views([view_part1, view_part2]); + + let mut dst = [0u8; 5]; + view_combined.copy_to_slice(&mut dst); + assert!(view_combined.is_empty()); + + assert_eq!(dst, [10_u8, 20, 30, 40, 50]); + } + + #[test] + fn copy_to_uninit_slice_multi_span() { + let memory = TransparentTestMemory::new(); + let data_part1 = [10_u8, 20]; + let data_part2 = [30_u8, 40, 50]; + let view_part1 = BytesView::copied_from_slice(&data_part1, &memory); + let view_part2 = BytesView::copied_from_slice(&data_part2, &memory); + let mut view_combined = BytesView::from_views([view_part1, view_part2]); + + let mut dst = [MaybeUninit::::uninit(); 5]; + view_combined.copy_to_uninit_slice(&mut dst); + assert!(view_combined.is_empty()); + + // SAFETY: It has now been initialized. + let dst = unsafe { std::mem::transmute::<[MaybeUninit; 5], [u8; 5]>(dst) }; + + assert_eq!(dst, [10_u8, 20, 30, 40, 50]); + } + + #[test] + fn get_num_le() { + let memory = TransparentTestMemory::new(); + let mut view = BytesView::copied_from_slice(&[0x34, 0x12, 0x78, 0x56], &memory); + + assert_eq!(view.get_num_le::(), 0x1234); + assert_eq!(view.get_num_le::(), 0x5678); + + assert!(view.is_empty()); + } + + #[test] + fn get_num_le_multi_span() { + let memory = TransparentTestMemory::new(); + let data_part1 = [0x78_u8, 0x56]; + let data_part2 = [0x34_u8, 0x12]; + let view_part1 = BytesView::copied_from_slice(&data_part1, &memory); + let view_part2 = BytesView::copied_from_slice(&data_part2, &memory); + let mut view_combined = BytesView::from_views([view_part1, view_part2]); + + assert_eq!(view_combined.get_num_le::(), 0x1234_5678); + + assert!(view_combined.is_empty()); + } + + #[test] + fn get_num_be() { + let memory = TransparentTestMemory::new(); + let mut view = BytesView::copied_from_slice(&[0x12, 0x34, 0x56, 0x78], &memory); + + assert_eq!(view.get_num_be::(), 0x1234); + assert_eq!(view.get_num_be::(), 0x5678); + + assert!(view.is_empty()); + } + + #[test] + fn get_num_be_multi_span() { + let memory = TransparentTestMemory::new(); + let data_part1 = [0x12_u8, 0x34]; + let data_part2 = [0x56_u8, 0x78]; + let view_part1 = BytesView::copied_from_slice(&data_part1, &memory); + let view_part2 = BytesView::copied_from_slice(&data_part2, &memory); + let mut view_combined = BytesView::from_views([view_part1, view_part2]); + + assert_eq!(view_combined.get_num_be::(), 0x1234_5678); + + assert!(view_combined.is_empty()); + } + + #[test] + fn get_num_ne() { + let memory = TransparentTestMemory::new(); + let mut view = BytesView::copied_from_slice(&[0x34, 0x12, 0x78, 0x56], &memory); + + if cfg!(target_endian = "big") { + assert_eq!(view.get_num_ne::(), 0x3412); + assert_eq!(view.get_num_ne::(), 0x7856); + } else { + assert_eq!(view.get_num_ne::(), 0x1234); + assert_eq!(view.get_num_ne::(), 0x5678); + } + + assert!(view.is_empty()); + } + + #[test] + fn get_num_ne_multi_span() { + let memory = TransparentTestMemory::new(); + let data_part1 = [0x78_u8, 0x56]; + let data_part2 = [0x34_u8, 0x12]; + let view_part1 = BytesView::copied_from_slice(&data_part1, &memory); + let view_part2 = BytesView::copied_from_slice(&data_part2, &memory); + let mut view_combined = BytesView::from_views([view_part1, view_part2]); + + if cfg!(target_endian = "big") { + assert_eq!(view_combined.get_num_ne::(), 0x7856_3412); + } else { + assert_eq!(view_combined.get_num_ne::(), 0x1234_5678); + } + + assert!(view_combined.is_empty()); + } +} diff --git a/crates/bytesbuf/src/write_adapter.rs b/crates/bytesbuf/src/write_adapter.rs index b1b3014a..df8b8132 100644 --- a/crates/bytesbuf/src/write_adapter.rs +++ b/crates/bytesbuf/src/write_adapter.rs @@ -3,27 +3,25 @@ use std::io::Write; -use bytes::BufMut; - use crate::{BytesBuf, Memory}; -/// Adapts a [`BytesBuf`] to implement the `std::io::Write` trait. +/// Adapter that implements `std::io::Write` for [`BytesBuf`]. /// -/// Instances are created via [`BytesBuf::as_write()`][1]. +/// Create an instance via [`BytesBuf::as_write()`][1]. /// /// The adapter will automatically extend the underlying sequence builder as needed when writing /// by allocating additional memory capacity from the memory provider `M`. /// /// [1]: crate::BytesBuf::as_write #[derive(Debug)] -pub(crate) struct BytesBufWrite<'sb, 'm, M: Memory> { - inner: &'sb mut BytesBuf, +pub(crate) struct BytesBufWrite<'b, 'm, M: Memory> { + inner: &'b mut BytesBuf, memory: &'m M, } -impl<'sb, 'm, M: Memory> BytesBufWrite<'sb, 'm, M> { +impl<'b, 'm, M: Memory> BytesBufWrite<'b, 'm, M> { #[must_use] - pub(crate) const fn new(inner: &'sb mut BytesBuf, memory: &'m M) -> Self { + pub(crate) const fn new(inner: &'b mut BytesBuf, memory: &'m M) -> Self { Self { inner, memory } } } @@ -32,7 +30,7 @@ impl Write for BytesBufWrite<'_, '_, M> { #[inline] fn write(&mut self, buf: &[u8]) -> std::io::Result { self.inner.reserve(buf.len(), self.memory); - self.inner.put(buf); + self.inner.put_slice(buf); Ok(buf.len()) } @@ -43,6 +41,7 @@ impl Write for BytesBufWrite<'_, '_, M> { } } +#[cfg_attr(coverage_nightly, coverage(off))] #[cfg(test)] mod tests { use new_zealand::nz; @@ -78,7 +77,7 @@ mod tests { // Add some initial content to the builder let initial_data = b"Initial content"; - builder.put_slice(initial_data); + builder.put_slice(initial_data.as_slice()); { let mut write_adapter = builder.as_write(&memory);