bytesbuf 0.1.2

Manipulate sequences of bytes for efficient I/O.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
<div align="center">
 <img src="./logo.png" alt="Byte BytesView Logo" width="128">

# Byte BytesView

[![crate.io](https://img.shields.io/crates/v/bytesbuf.svg)](https://crates.io/crates/bytesbuf)
[![docs.rs](https://docs.rs/bytesbuf/badge.svg)](https://docs.rs/bytesbuf)
[![MSRV](https://img.shields.io/crates/msrv/bytesbuf)](https://crates.io/crates/bytesbuf)
[![CI](https://github.com/microsoft/oxidizer/workflows/main/badge.svg)](https://github.com/microsoft/oxidizer/actions)
[![Coverage](https://codecov.io/gh/microsoft/oxidizer/graph/badge.svg?token=FCUG0EL5TI)](https://codecov.io/gh/microsoft/oxidizer)
[![License](https://img.shields.io/badge/license-MIT-blue.svg)](../LICENSE)

</div>

* [Summary]#summary
* [Consuming Byte BytesViews]#consuming-byte-sequences
* [Producing Byte BytesViews]#producing-byte-sequences
* [Automatically Extending BytesView Builder Capacity]#automatically-extending-sequence-builder-capacity
* [Implementing APIs that Consume Byte BytesViews]#implementing-apis-that-consume-byte-sequences
* [Compatibility with the `bytes` Crate]#compatibility-with-the-bytes-crate
* [Static Data]#static-data
* [Testing]#testing

## Summary

<!-- cargo-rdme start -->

Manipulate sequences of bytes 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:

* 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`].

In practical terms, you may think of a byte sequence as a `Vec<Vec<u8>>` 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.

## 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`].

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.

```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();
        sum = sum.saturating_add(word);
    }

    println!("Message received. The sum of all words in the message is {sum}.");
}
```

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:

* [`chunk()`][21], which returns a slice of bytes from the beginning of the 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.

```rust
use bytes::Buf;
use bytesbuf::BytesView;

let len = sequence.len();
let mut chunk_lengths = Vec::new();

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:?}");
```

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);

let mut sequence_clone = sequence.clone();
assert_eq!(sequence_clone.len(), 16);

_ = sequence_clone.get_u64();
assert_eq!(sequence_clone.len(), 8);

// Operations on the clone have no effect on the original sequence.
assert_eq!(sequence.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`] 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`] trait, which gives you a suitable memory
   provider instance via [`HasMemory::memory()`][25]. Use it - this memory provider will
   give you memory with the configuration that is optimal for delivering bytes to that
   specific instance.
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()`.

Once you have a memory provider, you can reserve memory from it by calling
[`Memory::reserve()`][14] on it. This returns a [`BytesBuf`] with the requested
memory capacity.

```rust
use bytesbuf::Memory;

let memory = connection.memory();

let mut sequence_builder = 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`].

Helper methods on this trait allow you to write bytes to the sequence builder up to the
extent of the reserved memory capacity.

```rust
use bytes::buf::BufMut;
use bytesbuf::Memory;

let memory = connection.memory();

let mut sequence_builder = memory.reserve(100);

sequence_builder.put_u64(1234);
sequence_builder.put_u64(5678);
sequence_builder.put(b"Hello, world!".as_slice());
```

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:

* [`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.

See `examples/mem_chunk_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()`][13],
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.

```rust
use bytes::buf::BufMut;
use bytesbuf::Memory;

let memory = connection.memory();

let mut sequence_builder = memory.reserve(100);

// .. write some data into the sequence builder ..

// We discover that we need 80 additional bytes of memory! No problem.
sequence_builder.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);
```

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.

```rust
use bytes::buf::BufMut;
use bytesbuf::Memory;

let memory = connection.memory();

let mut sequence_builder = memory.reserve(100);

sequence_builder.put_u64(1234);
sequence_builder.put_u64(5678);
sequence_builder.put(b"Hello, world!".as_slice());

let message = sequence_builder.consume_all();
```

This can be done piece by piece, and you can continue writing to the sequence builder
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);

sequence_builder.put_u64(1234);
sequence_builder.put_u64(5678);

let first_8_bytes = sequence_builder.consume(8);
let second_8_bytes = sequence_builder.consume(8);

sequence_builder.put(b"Hello, world!".as_slice());

let final_contents = sequence_builder.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.

```rust
use bytes::buf::BufMut;
use bytesbuf::Memory;

let memory = connection.memory();

let mut header_builder = memory.reserve(16);
header_builder.put_u64(1234);
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());
```

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
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.

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`], 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`] is as follows:

* If your type always passes the data to another type that implements [`HasMemory`],
  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`]
  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`
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.
///
/// # Implementation strategy for `HasMemory`
///
/// This type merely inspects a byte sequence before passing it on. This means that it does not
/// have a preference of its own for how that memory should be configured.
///
/// However, the thing it passes the sequence to (the `Connection` type) may have a preference,
/// so we forward the memory provider of the `Connection` type as our own memory provider, so the
/// caller can use memory optimal for submission to the `Connection` instance.
#[derive(Debug)]
struct ConnectionZeroCounter {
    connection: Connection,
}

impl ConnectionZeroCounter {
    pub fn new(connection: Connection) -> Self {
        Self {
            connection,
        }
    }

    pub fn write(&mut self, sequence: BytesView) {
        // TODO: Count zeros.

        self.connection.write(sequence);
    }
}

impl HasMemory for ConnectionZeroCounter {
    fn memory(&self) -> impl MemoryShared {
        // We forward the memory provider of the connection, so that the caller can use
        // memory optimal for submission to the connection.
        self.connection.memory()
    }
}
```

Example of returning a memory provider that performs configuration for optimal memory (see
`examples/mem_has_provider_optimizing.rs` for full code):

```rust
use bytesbuf::{CallbackMemory, HasMemory, MemoryShared, BytesView};

/// # Implementation strategy for `HasMemory`
///
/// This type can benefit from optimal performance if specifically configured memory is used and
/// 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.
#[derive(Debug)]
struct UdpConnection {
    io_context: IoContext,
}

impl UdpConnection {
    pub fn new(io_context: IoContext) -> Self {
        Self { io_context }
    }
}

/// Represents the optimal memory configuration for a UDP connection when reserving I/O memory.
const UDP_CONNECTION_OPTIMAL_MEMORY_CONFIGURATION: MemoryConfiguration = MemoryConfiguration {
    requires_page_alignment: false,
    zero_memory_on_release: false,
    requires_registered_memory: true,
};

impl HasMemory for UdpConnection {
    fn memory(&self) -> impl MemoryShared {
        CallbackMemory::new({
            // Cloning is cheap, as it is a service that shares resources between clones.
            let io_context = self.io_context.clone();

            move |min_len| {
                io_context.reserve_io_memory(min_len, UDP_CONNECTION_OPTIMAL_MEMORY_CONFIGURATION)
            }
        })
    }
}

```

Example of returning a usage-neutral memory provider (see `examples/mem_has_provider_neutral.rs` for
full code):

```rust
use bytesbuf::{GlobalPool, HasMemory, MemoryShared};

/// Calculates a checksum for a given byte sequence.
///
/// # Implementation strategy for `HasMemory`
///
/// This type does not benefit from any specific memory configuration - it consumes bytes no
/// matter what sort of memory they are in. It also does not pass the bytes to some other type.
///
/// Therefore, we simply use `GlobalPool` as the memory provider we publish, as this is
/// the default choice when there is no specific provider to prefer.
#[derive(Debug)]
struct ChecksumCalculator {
    // The application logic must provide this - it is our dependency.
    memory_provider: GlobalPool,
}

impl ChecksumCalculator {
    pub fn new(memory_provider: GlobalPool) -> Self {
        Self { memory_provider }
    }
}

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()
    }
}
```

It is generally expected that all APIs work with byte sequences using memory from any provider.
It is true that in some cases this may be impossible (e.g. because you are interacting directly
with a device driver that requires the data to be in a specific physical memory module) but
these cases will be rare and must be explicitly documented.

If your type can take advantage of optimizations enabled by specific memory configurations,
it needs to determine whether a byte sequence actually uses the desired memory configuration.
This can be done by inspecting the provided byte sequence and the memory metadata it exposes.
If the metadata indicates a suitable configuration, the optimal implementation can be used.
Otherwise, the implementation can fall back to a generic implementation that works with any
byte sequence.

Example of identifying whether a byte sequence uses the optimal memory configuration (see
`examples/mem_optimal_path.rs` for full code):

```rust
use bytesbuf::BytesView;

pub fn write(&mut self, message: BytesView) {
    // We now need to identify whether the message actually uses memory that allows us to
    // 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| {
        // 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
            // exposed by the I/O memory provider, the memory is not I/O memory.
            let Some(io_memory_configuration) = meta.downcast_ref::<MemoryConfiguration>()
            else {
                return false;
            };

            // If the memory is I/O memory but is not not pre-registered
            // with the operating system, we cannot use the optimal path.
            io_memory_configuration.requires_registered_memory
        })
    });

    if use_optimal_path {
        self.write_optimal(message);
    } else {
        self.write_fallback(message);
    }
}
```

Note that there is no requirement that a byte sequence consists of homogeneous memory. Different
parts of the byte sequence may come from different memory providers, so all chunks must be
checked for compatibility.

## Compatibility with the `bytes` Crate

The popular [`Bytes`][18] 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]:

* [`BytesView::into_bytes()`][16] converts a [`BytesView`] into a [`Bytes`][18] 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.
* See `Work Item 5861368: BytesView::into_bytes_iter()`
* `BytesView::from(Bytes)` or `let s: BytesView = bytes.into()` converts a [`Bytes`][18] instance
  into a [`BytesView`]. This is an efficient zero-copy operation that reuses the memory of the
  `Bytes` instance.

## Static Data

You may have static data in your logic, such as the names/prefixes of request/response headers:

```rust
const HEADER_PREFIX: &[u8] = b"Unix-Milliseconds: ";
```

Optimal processing of static data requires satisfying multiple requirements:

* We want zero-copy processing when consuming this data.
* 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 static data on first use, using memory from a memory provider that is optimal for the
intended usage.

```rust
use std::sync::OnceLock;

use bytesbuf::BytesView;

const HEADER_PREFIX: &[u8] = b"Unix-Milliseconds: ";

// 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::<BytesView>::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.
    let header_prefix = header_prefix
        .get_or_init(|| BytesView::copied_from_slice(HEADER_PREFIX, &connection.memory()));

    // Now we can use the `header_prefix` BytesView in the connection logic.
    // Cloning a BytesView is a cheap zero-copy operation.
    connection.write(header_prefix.clone());
}
```

Different usages (e.g. file vs network) may require differently configured memory for optimal
performance, so you may need a different `BytesView` if the same static data is to be used
in different contexts.

## Testing

For testing purposes, this crate exposes some special-purpose memory providers that are not
optimized for real-world usage but may be useful to test corner cases of byte sequence
processing in your code:

* [`TransparentTestMemory`] - a memory provider that does not add any value, just uses memory
  from the Rust global allocator.
* [`FixedBlockTestMemory`] - 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!

[13]: BytesBuf::reserve
[14]: Memory::reserve
[16]: BytesView::into_bytes
[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
[25]: HasMemory::memory
[26]: https://docs.rs/bytes/latest/bytes/buf/trait.BufMut.html#method.remaining_mut
[27]: std::sync::OnceLock

<!-- cargo-rdme end -->

<div style="font-size: 75%" ><hr/>

This crate was developed as part of [The Oxidizer Project](https://github.com/microsoft/oxidizer).

</div>