smol-bytes 0.2.0

High-performance, clone-efficient byte buffers optimized for small data.
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
<div align="center">
<h1>smol-bytes</h1>
</div>
<div align="center">

High-performance, clone-efficient byte buffers optimized for small data.

[<img alt="github" src="https://img.shields.io/badge/github-al8n/smol--bytes-8da0cb?style=for-the-badge&logo=Github" height="22">][Github-url]
<img alt="LoC" src="https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2Fal8n%2F327b2a8aef9003246e45c6e47fe63937%2Fraw%2Fsmol-bytes" height="22">
[<img alt="Build" src="https://img.shields.io/github/actions/workflow/status/al8n/smol-bytes/ci.yml?logo=Github-Actions&style=for-the-badge" height="22">][CI-url]
[<img alt="codecov" src="https://img.shields.io/codecov/c/gh/al8n/smol-bytes?style=for-the-badge&token=6R3QFWRWHL&logo=codecov" height="22">][codecov-url]

[<img alt="docs.rs" src="https://img.shields.io/badge/docs.rs-smol--bytes-66c2a5?style=for-the-badge&labelColor=555555&logo=data:image/svg+xml;base64,PHN2ZyByb2xlPSJpbWciIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmlld0JveD0iMCAwIDUxMiA1MTIiPjxwYXRoIGZpbGw9IiNmNWY1ZjUiIGQ9Ik00ODguNiAyNTAuMkwzOTIgMjE0VjEwNS41YzAtMTUtOS4zLTI4LjQtMjMuNC0zMy43bC0xMDAtMzcuNWMtOC4xLTMuMS0xNy4xLTMuMS0yNS4zIDBsLTEwMCAzNy41Yy0xNC4xIDUuMy0yMy40IDE4LjctMjMuNCAzMy43VjIxNGwtOTYuNiAzNi4yQzkuMyAyNTUuNSAwIDI2OC45IDAgMjgzLjlWMzk0YzAgMTMuNiA3LjcgMjYuMSAxOS45IDMyLjJsMTAwIDUwYzEwLjEgNS4xIDIyLjEgNS4xIDMyLjIgMGwxMDMuOS01MiAxMDMuOSA1MmMxMC4xIDUuMSAyMi4xIDUuMSAzMi4yIDBsMTAwLTUwYzEyLjItNi4xIDE5LjktMTguNiAxOS45LTMyLjJWMjgzLjljMC0xNS05LjMtMjguNC0yMy40LTMzLjd6TTM1OCAyMTQuOGwtODUgMzEuOXYtNjguMmw4NS0zN3Y3My4zek0xNTQgMTA0LjFsMTAyLTM4LjIgMTAyIDM4LjJ2LjZsLTEwMiA0MS40LTEwMi00MS40di0uNnptODQgMjkxLjFsLTg1IDQyLjV2LTc5LjFsODUtMzguOHY3NS40em0wLTExMmwtMTAyIDQxLjQtMTAyLTQxLjR2LS42bDEwMi0zOC4yIDEwMiAzOC4ydi42em0yNDAgMTEybC04NSA0Mi41di03OS4xbDg1LTM4Ljh2NzUuNHptMC0xMTJsLTEwMiA0MS40LTEwMi00MS40di0uNmwxMDItMzguMiAxMDIgMzguMnYuNnoiPjwvcGF0aD48L3N2Zz4K" height="20">][doc-url]
[<img alt="crates.io" src="https://img.shields.io/crates/v/smol-bytes?style=for-the-badge&logo=data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iaXNvLTg4NTktMSI/Pg0KPCEtLSBHZW5lcmF0b3I6IEFkb2JlIElsbHVzdHJhdG9yIDE5LjAuMCwgU1ZHIEV4cG9ydCBQbHVnLUluIC4gU1ZHIFZlcnNpb246IDYuMDAgQnVpbGQgMCkgIC0tPg0KPHN2ZyB2ZXJzaW9uPSIxLjEiIGlkPSJMYXllcl8xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB4PSIwcHgiIHk9IjBweCINCgkgdmlld0JveD0iMCAwIDUxMiA1MTIiIHhtbDpzcGFjZT0icHJlc2VydmUiPg0KPGc+DQoJPGc+DQoJCTxwYXRoIGQ9Ik0yNTYsMEwzMS41MjgsMTEyLjIzNnYyODcuNTI4TDI1Niw1MTJsMjI0LjQ3Mi0xMTIuMjM2VjExMi4yMzZMMjU2LDB6IE0yMzQuMjc3LDQ1Mi41NjRMNzQuOTc0LDM3Mi45MTNWMTYwLjgxDQoJCQlsMTU5LjMwMyw3OS42NTFWNDUyLjU2NHogTTEwMS44MjYsMTI1LjY2MkwyNTYsNDguNTc2bDE1NC4xNzQsNzcuMDg3TDI1NiwyMDIuNzQ5TDEwMS44MjYsMTI1LjY2MnogTTQzNy4wMjYsMzcyLjkxMw0KCQkJbC0xNTkuMzAzLDc5LjY1MVYyNDAuNDYxbDE1OS4zMDMtNzkuNjUxVjM3Mi45MTN6IiBmaWxsPSIjRkZGIi8+DQoJPC9nPg0KPC9nPg0KPGc+DQo8L2c+DQo8Zz4NCjwvZz4NCjxnPg0KPC9nPg0KPGc+DQo8L2c+DQo8Zz4NCjwvZz4NCjxnPg0KPC9nPg0KPGc+DQo8L2c+DQo8Zz4NCjwvZz4NCjxnPg0KPC9nPg0KPGc+DQo8L2c+DQo8Zz4NCjwvZz4NCjxnPg0KPC9nPg0KPGc+DQo8L2c+DQo8Zz4NCjwvZz4NCjxnPg0KPC9nPg0KPC9zdmc+DQo=" height="22">][crates-url]
[<img alt="crates.io" src="https://img.shields.io/crates/d/smol-bytes?color=critical&logo=data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBzdGFuZGFsb25lPSJubyI/PjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+PHN2ZyB0PSIxNjQ1MTE3MzMyOTU5IiBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHAtaWQ9IjM0MjEiIGRhdGEtc3BtLWFuY2hvci1pZD0iYTMxM3guNzc4MTA2OS4wLmkzIiB3aWR0aD0iNDgiIGhlaWdodD0iNDgiIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIj48ZGVmcz48c3R5bGUgdHlwZT0idGV4dC9jc3MiPjwvc3R5bGU+PC9kZWZzPjxwYXRoIGQ9Ik00NjkuMzEyIDU3MC4yNHYtMjU2aDg1LjM3NnYyNTZoMTI4TDUxMiA3NTYuMjg4IDM0MS4zMTIgNTcwLjI0aDEyOHpNMTAyNCA2NDAuMTI4QzEwMjQgNzgyLjkxMiA5MTkuODcyIDg5NiA3ODcuNjQ4IDg5NmgtNTEyQzEyMy45MDQgODk2IDAgNzYxLjYgMCA1OTcuNTA0IDAgNDUxLjk2OCA5NC42NTYgMzMxLjUyIDIyNi40MzIgMzAyLjk3NiAyODQuMTYgMTk1LjQ1NiAzOTEuODA4IDEyOCA1MTIgMTI4YzE1Mi4zMiAwIDI4Mi4xMTIgMTA4LjQxNiAzMjMuMzkyIDI2MS4xMkM5NDEuODg4IDQxMy40NCAxMDI0IDUxOS4wNCAxMDI0IDY0MC4xOTJ6IG0tMjU5LjItMjA1LjMxMmMtMjQuNDQ4LTEyOS4wMjQtMTI4Ljg5Ni0yMjIuNzItMjUyLjgtMjIyLjcyLTk3LjI4IDAtMTgzLjA0IDU3LjM0NC0yMjQuNjQgMTQ3LjQ1NmwtOS4yOCAyMC4yMjQtMjAuOTI4IDIuOTQ0Yy0xMDMuMzYgMTQuNC0xNzguMzY4IDEwNC4zMi0xNzguMzY4IDIxNC43MiAwIDExNy45NTIgODguODMyIDIxNC40IDE5Ni45MjggMjE0LjRoNTEyYzg4LjMyIDAgMTU3LjUwNC03NS4xMzYgMTU3LjUwNC0xNzEuNzEyIDAtODguMDY0LTY1LjkyLTE2NC45MjgtMTQ0Ljk2LTE3MS43NzZsLTI5LjUwNC0yLjU2LTUuODg4LTMwLjk3NnoiIGZpbGw9IiNmZmZmZmYiIHAtaWQ9IjM0MjIiIGRhdGEtc3BtLWFuY2hvci1pZD0iYTMxM3guNzc4MTA2OS4wLmkwIiBjbGFzcz0iIj48L3BhdGg+PC9zdmc+&style=for-the-badge" height="22">][crates-url]
<img alt="license" src="https://img.shields.io/badge/License-Apache%202.0/MIT-blue.svg?style=for-the-badge&fontColor=white&logoColor=f5c076&logo=data:image/svg+xml;base64,PCFET0NUWVBFIHN2ZyBQVUJMSUMgIi0vL1czQy8vRFREIFNWRyAxLjEvL0VOIiAiaHR0cDovL3d3dy53My5vcmcvR3JhcGhpY3MvU1ZHLzEuMS9EVEQvc3ZnMTEuZHRkIj4KDTwhLS0gVXBsb2FkZWQgdG86IFNWRyBSZXBvLCB3d3cuc3ZncmVwby5jb20sIFRyYW5zZm9ybWVkIGJ5OiBTVkcgUmVwbyBNaXhlciBUb29scyAtLT4KPHN2ZyBmaWxsPSIjZmZmZmZmIiBoZWlnaHQ9IjgwMHB4IiB3aWR0aD0iODAwcHgiIHZlcnNpb249IjEuMSIgaWQ9IkNhcGFfMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmlld0JveD0iMCAwIDI3Ni43MTUgMjc2LjcxNSIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSIgc3Ryb2tlPSIjZmZmZmZmIj4KDTxnIGlkPSJTVkdSZXBvX2JnQ2FycmllciIgc3Ryb2tlLXdpZHRoPSIwIi8+Cg08ZyBpZD0iU1ZHUmVwb190cmFjZXJDYXJyaWVyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz4KDTxnIGlkPSJTVkdSZXBvX2ljb25DYXJyaWVyIj4gPGc+IDxwYXRoIGQ9Ik0xMzguMzU3LDBDNjIuMDY2LDAsMCw2Mi4wNjYsMCwxMzguMzU3czYyLjA2NiwxMzguMzU3LDEzOC4zNTcsMTM4LjM1N3MxMzguMzU3LTYyLjA2NiwxMzguMzU3LTEzOC4zNTcgUzIxNC42NDgsMCwxMzguMzU3LDB6IE0xMzguMzU3LDI1OC43MTVDNzEuOTkyLDI1OC43MTUsMTgsMjA0LjcyMywxOCwxMzguMzU3UzcxLjk5MiwxOCwxMzguMzU3LDE4IHMxMjAuMzU3LDUzLjk5MiwxMjAuMzU3LDEyMC4zNTdTMjA0LjcyMywyNTguNzE1LDEzOC4zNTcsMjU4LjcxNXoiLz4gPHBhdGggZD0iTTE5NC43OTgsMTYwLjkwM2MtNC4xODgtMi42NzctOS43NTMtMS40NTQtMTIuNDMyLDIuNzMyYy04LjY5NCwxMy41OTMtMjMuNTAzLDIxLjcwOC0zOS42MTQsMjEuNzA4IGMtMjUuOTA4LDAtNDYuOTg1LTIxLjA3OC00Ni45ODUtNDYuOTg2czIxLjA3Ny00Ni45ODYsNDYuOTg1LTQ2Ljk4NmMxNS42MzMsMCwzMC4yLDcuNzQ3LDM4Ljk2OCwyMC43MjMgYzIuNzgyLDQuMTE3LDguMzc1LDUuMjAxLDEyLjQ5NiwyLjQxOGM0LjExOC0yLjc4Miw1LjIwMS04LjM3NywyLjQxOC0xMi40OTZjLTEyLjExOC0xNy45MzctMzIuMjYyLTI4LjY0NS01My44ODItMjguNjQ1IGMtMzUuODMzLDAtNjQuOTg1LDI5LjE1Mi02NC45ODUsNjQuOTg2czI5LjE1Miw2NC45ODYsNjQuOTg1LDY0Ljk4NmMyMi4yODEsMCw0Mi43NTktMTEuMjE4LDU0Ljc3OC0zMC4wMDkgQzIwMC4yMDgsMTY5LjE0NywxOTguOTg1LDE2My41ODIsMTk0Ljc5OCwxNjAuOTAzeiIvPiA8L2c+IDwvZz4KDTwvc3ZnPg==" height="22">

[<img alt="Discord" src="https://img.shields.io/discord/835936528140206122?style=for-the-badge&logo=discord&logoColor=white&label=Discord&color=7289da" height="22">][discord]

</div>

## Introduction

`smol-bytes` provides byte buffers that store up to 62 bytes inline — no heap
allocation, and cloning an inline value is a plain 64-byte copy. Larger values
use [`bytes`](https://crates.io/crates/bytes)-backed heap storage with
reference-counted clones. Two strategies control what happens when a heap
value shrinks back under the inline threshold, and UTF-8 wrappers layer
`String`-like, boundary-checked APIs over the same storage.

This is a good fit when most values are small and cloned often — tokens in a
lexer, keys and field names, protocol headers — and allocation pressure
matters.

## Install

```toml
[dependencies]
smol-bytes = "0.2"
```

Python (`pip install smol-bytes`) and JavaScript (`npm install smol-bytes`)
packages are also published; build-from-source instructions for each binding
are below.

## Quick start

The root `Bytes` type is an alias for `shared::Bytes`. Ordinary copies and
static values of at most 62 bytes start inline; imported or owner-backed
values and retained shared heap views can remain heap-backed below that
threshold.

```rust
use smol_bytes::{Bytes, Utf8Bytes};

let small = Bytes::from_static(b"identifier");
assert!(small.is_inline());

let cloned = small.clone(); // 64-byte copy, no allocation
assert_eq!(small, cloned);

let heap = Bytes::copy_from_slice(&[0_u8; 63]);
assert!(heap.is_heap());
assert_eq!(heap.len(), 63);

let text: Utf8Bytes = "identifier".parse().expect("infallible parse");
assert_eq!(text.as_str(), "identifier");
```

## Storage strategies

`shared::Bytes` preserves heap storage once a value lives there, keeping
conversions with `bytes::Bytes` zero-copy. `compact::Bytes` copies a shrinking
view of 62 bytes or fewer back into inline storage to release the allocation.

```rust
use smol_bytes::{compact, shared, Buf};

let mut shared = shared::Bytes::from(vec![0_u8; 100]);
let mut compact = compact::Bytes::from(vec![0_u8; 100]);

shared.advance(70);
compact.advance(70);

assert_eq!(shared.len(), 30);
assert_eq!(compact.len(), 30);
assert!(shared.is_heap());   // stays shareable and zero-copy convertible
assert!(compact.is_inline()); // allocation released, contents inlined
```

Rule of thumb: use `shared` (the default) for I/O and `bytes` interop; use
`compact` when memory footprint matters more than conversion speed.

## Types

| Type | Storage | Mutable | Purpose |
| --- | --- | :---: | --- |
| `Buffer` | Fixed inline bytes, up to 62 bytes | Yes | `no_std` fixed buffer |
| `Bytes` / `shared::Bytes` | Inline or shared heap-backed bytes | No | Immutable shared view |
| `compact::Bytes` | Inline or compacting heap-backed bytes | No | Immutable compacting view |
| `BytesMut` | Inline, then growable `bytes::BytesMut` storage | Yes | Mutable byte buffer |
| `Utf8Buffer` | Fixed inline, valid UTF-8 bytes | Yes | Small mutable UTF-8 value |
| `Utf8Bytes` / `compact::Utf8Bytes` | Shared or compacting UTF-8 bytes | No | Immutable UTF-8 value |
| `Utf8BytesMut` | Inline or growable valid UTF-8 bytes | Yes | Mutable UTF-8 value |

Every handle is 64 bytes. With the `bytes` dependency enabled, all byte types
implement `bytes::Buf`, and the mutable ones implement `bytes::BufMut`.

The immutable strategy types have immutable payloads but mutable views:
`advance`, `truncate`, and `clear` change only the visible cursor/window.
`Utf8Bytes` offers the same byte-indexed, UTF-8-boundary-checked view
operations and can reclaim unique full heap allocations with `try_into_mut`.
`Utf8Buffer` and `Utf8BytesMut` expose `as_mut_str`, `AsMut<str>`,
`BorrowMut<str>`, `DerefMut<Target = str>`, and mutable string-range indexing;
the standard `str` rules preserve UTF-8 validity.

`BytesMut::split_to`, `split_off`, and `split` always return `BytesMut`.
Inline results stay inline; splitting an inline value's reserved-but-unwritten
capacity promotes it to heap storage so capacity partitioning remains exact.
`try_split_to` and `try_split_off` return `Result<BytesMut, OutOfBounds>` and
leave the receiver unchanged on bounds failure. `unsplit` always consumes and
appends its argument, using the zero-copy `bytes::BytesMut` path for contiguous
heap halves and copying/promoting only when required.

Rust UTF-8 split and slice indices are byte offsets that must fall on
character boundaries; offenders panic, and the `try_split_to`,
`try_split_off`, and `try_slice` variants return errors instead. (The Python
bindings differ deliberately — see below.)

The UTF-8 wrappers implement `FromStr`. Heap-capable wrappers parse
infallibly; `Utf8Buffer` returns `TryPutError` when text exceeds its fixed
inline capacity.

## 0.2 API migration

0.2 intentionally removes several ambiguous or lossy APIs.
For complete Rust, Python, and JavaScript examples, see
[the 0.2.0 changelog and migration guide](https://github.com/al8n/smol-bytes/blob/main/CHANGELOG.md#020---2026-10-18).

- Replace `Utf8Buffer::from(text)` with `Utf8Buffer::try_from(text)?` (or
  `Utf8Buffer::try_from_str(text)?`). `FromStr` remains available. This makes
  the 62-byte fixed-capacity limit explicit.
- `BytesMut` split methods now return `BytesMut` directly. Remove an inner
  `match`/`unwrap` around `split`, `split_to`, and `split_off`; use the
  corresponding `try_split_*` method when bounds must be reported instead of
  panicking. `try_split` is removed, and `unsplit` now returns `()`.
- Use `Buffer::resize(new_len, value)` and
  `Buffer::try_resize(new_len, value)` to choose the byte written during
  extension. Failed `try_resize` calls are atomic.
- UTF-8 index operations now use `Utf8IndexError`, preserving whether an
  invalid character boundary, index, or full range caused the failure.
  Converting owned `Buffer`, `BytesMut`, or raw strategy bytes into a UTF-8
  wrapper returns `FromUtf8Error<B>` on invalid input; call `into_bytes()` to
  recover the original allocation and view.
- Variable-width inherent `Buffer::try_get_uint{,_le,_ne}` and
  `Buffer::try_get_int{,_le,_ne}` methods now
  return `TryGetIntegerError`, distinguishing an invalid width from missing
  bytes. The `bytes::Buf` trait methods retain `bytes`' own error type and
  panic contract.
- `Utf8Buf` and `Utf8BufMut` are sealed implementation traits. Generic mutable
  code can use `try_push`/`try_push_str` and each type's `CapacityError`;
  `Utf8Buffer` reports `TryPutError`, while `Utf8BytesMut` uses `Infallible`.

## `bytes` interop

Conversions with the `bytes` crate are zero-copy wherever the representation
allows:

- `bytes::Bytes -> shared::Bytes` shares the allocation (`From` impl).
- `shared::Bytes -> bytes::Bytes` reuses heap backing; inline values copy.
- `compact::Bytes::from(bytes::Bytes)` inlines payloads of at most 62 bytes
  and shares larger ones.
- `BytesMut::freeze_shared` / `freeze_compact` convert without copying heap
  contents; `BytesMut::freeze` is the root-default shared conversion.
- `Utf8BytesMut::freeze` similarly returns the root-default `Utf8Bytes`;
  `freeze_shared` and `freeze_compact` remain available for explicit targets.
- Consuming `shared::Bytes`/`compact::Bytes` and UTF-8 strategy conversions
  apply the target strategy: large heap values keep their allocation and small
  values compact when converted to the compact strategy.
- `Bytes::try_into_mut` and `Utf8Bytes::try_into_mut` reclaim unique full heap
  allocations; a shared or sliced view is returned unchanged on failure.

```rust
use smol_bytes::{BytesMut, Utf8Bytes, Utf8BytesMut, compact};

let bytes = BytesMut::from(&b"payload"[..]).freeze();
assert_eq!(bytes, b"payload"[..]);

let mut text = Utf8Bytes::from("header: value");
text.advance(8);
assert_eq!(text.as_str(), "value"); // only this view moved

let compact: compact::Bytes = bytes.into();
assert_eq!(compact, b"payload"[..]);

let frozen = Utf8BytesMut::from("done").freeze();
assert_eq!(frozen.as_str(), "done");
```

## Features and MSRV

| Feature | Description |
| --- | --- |
| `std` (default) | Enables `alloc`, `bytes/std`, and Serde's `std`/`result` support |
| `alloc` | Heap-backed types and the optional `bytes` dependency without `std` |
| `bytes` | Compatibility alias for `alloc` |
| `serde` | Serde support |
| `schemars` | Schemars v1 schemas; UTF-8 wrappers are strings and byte wrappers serialize as `array<u8>`; implies `alloc` |
| `borsh` | Borsh support |
| `arbitrary` | `arbitrary` support for generated values |
| `quickcheck` | QuickCheck support; implies `std` |
| `async-graphql` | `Bytes` and `String` GraphQL scalars for `Bytes` and `Utf8Bytes`; implies `std` |
| `sqlx` | `sqlx` `Type`/`Encode`/`Decode` for `Bytes` and `Utf8Bytes`; implies `std` |
| `pyo3` | Python bindings; implies `std` |
| `wasm` | WebAssembly bindings; implies `std` |

With no features enabled the crate is `no_std` and provides the fixed
`Buffer` and `Utf8Buffer` types. `bytes::Buf`/`BufMut` are available once
`alloc`, `std`, or the compatibility `bytes` feature is selected; `alloc` adds
the heap-backed types without `std`.

Byte wrapper JSON schemas describe serialization as `array<u8>` and accept an
array or string under Schemars' deserialization contract. `Buffer` additionally
sets `maxItems` for array input and `maxLength` for string input to `INLINE_CAP`.
`maxLength` is a scalar-count bound; deserialization still enforces the actual
UTF-8 byte capacity for multibyte strings. UTF-8 wrappers remain ordinary string
schemas.

The `sqlx` bindings decode by borrowing from the row, so a value of at most 62
bytes is built inline with no heap allocation at all. That costs one thing:
PostgreSQL will not lend `BYTEA` out in a simple (unprepared) query, so code on
that path — `raw_sql`, or a SQL string handed straight to an `Executor` — has
to decode the byte types as `Vec<u8>` and convert. `query`, `query_as` and the
`query!` macros carry an argument list and are therefore prepared, so they are
unaffected, as are the UTF-8 types and every MySQL and SQLite path.

Rust 1.95 is the library MSRV, and the `bytes` dependency floor is 1.10.
Development-only test and benchmark dependencies can require a newer
compiler, and so do both optional integrations, to different floors:
`async-graphql` 7.2 declares Rust 1.89, and 7.2 is the floor because the 7.0
releases do not build against the 7.2 derive crate their own dependency range
admits; sqlx 0.9 declares Rust 1.94, and 0.9 is the floor because the impls are
written against the lifetime-free `Database::ArgumentBuffer` introduced there.
Enabling either raises the MSRV for the whole build.

## Verification

The test and CI story is deliberately heavier than the crate's size:

- Unit, integration, doc, and property tests (proptest state-machine
  comparisons against `Vec`/`String`, plus `quickcheck` and `arbitrary`
  generators that preserve type invariants).
- Miri over the full suite under both stacked borrows and tree borrows with
  strict provenance and symbolic alignment checks.
- Address, leak, memory, and thread sanitizers in CI.
- Deserialization is hardened: borsh reads length-prefixed payloads in
  bounded chunks instead of trusting the length prefix, and serde sequence
  hints are capped before preallocating.

## Python

Install the published package with `pip install smol-bytes` (Python 3.11+).

To build from a checkout instead, Python 3.11+, Rust, and `maturin` are
required:

```bash
python -m venv .venv
source .venv/bin/activate
python -m pip install maturin pytest
maturin develop --features pyo3 --manifest-path smol-bytes-py/Cargo.toml
python -m pytest tests/python -v
```

The root `smol_bytes` module exposes canonical shared `Bytes` and `Utf8Bytes`
alongside `Buffer`, `BytesMut`, `Utf8Buffer`, and `Utf8BytesMut`.
`smol_bytes.shared.Bytes` and `smol_bytes.shared.Utf8Bytes` are aliases of
those exact root classes (including their root `__module__`), while
`smol_bytes.compact` remains a distinct compact strategy.

Raw byte classes use `from_utf8()` and `decode_utf8()` for explicit text
conversion; `str(raw)` is always the same total representation as
`str(bytes(raw))`. Their `from_bytes()` and mutable `put_slice()` methods
accept `bytes`, `bytearray`, and readable one-dimensional C-contiguous byte
`memoryview` values, copying the input before mutation.

The UTF-8 classes are string-like from Python: `len()`, indexing, and
iteration use Unicode scalar values. Their explicit APIs are `char_len()`,
`byte_len()`, `split_to_chars()`, `split_off_chars()`, `slice_chars()`, and
for mutable/fixed classes `truncate_chars()`. `remaining_bytes()` and
`advance_bytes()` are deliberately byte-oriented, as are `get_*` methods.

```python
from smol_bytes import Bytes, BytesMut, Utf8Bytes

raw = Bytes.from_bytes(b"abc")
assert raw.is_inline()
assert bytes(raw) == b"abc"
assert raw.decode_utf8() == "abc"

text = Utf8Bytes.from_str("café")
assert len(text) == 4        # Unicode characters
assert text.char_len() == 4
assert text.byte_len() == 5  # UTF-8 bytes
assert str(text) == "café"
assert str(text.split_to_chars(3)) == "caf"

mutable = BytesMut.from_bytes(bytearray(b"abc"))
frozen = mutable.freeze_shared()
assert bytes(mutable) == b""  # source remains reusable
assert bytes(frozen.to_mutable()) == b"abc"
```

Binding behavior worth knowing:

- Methods that allocate proportionally to caller data raise `MemoryError`
  on absurd or failing requests instead of aborting the interpreter, like
  CPython containers.
- `memoryview(...)` over the shared and compact `Bytes` classes exports a
  snapshot copy, not a live view.
- Slice assignment on the mutable classes requires matching lengths, and
  contiguous assignments take a direct copy fast path.
- Python `unsplit()` uses non-consuming snapshot semantics: it appends the
  call-entry contents while leaving the argument unchanged. Passing the same
  mutable object as both operands raises `RuntimeError` atomically.

## JavaScript / WebAssembly

Install the published package with `npm install smol-bytes`.

To build from a checkout instead, install Node.js 20, `wasm-pack` 0.13.1, and
the `wasm32-unknown-unknown` target. wasm-pack and Cargo resolve a compatible
wasm-bindgen 0.2.x release; any separately installed wasm-bindgen helper must
match the resolved crate version. CI checks the supported 0.2.x family.

```bash
rustup target add wasm32-unknown-unknown
cd js
npm ci
npm run build
npm test
```

The Wasm build entry point is:

```bash
wasm-pack build smol-bytes-wasm --target bundler --out-dir ../js/pkg --out-name smol_bytes -- --features wasm
```

The root export canonically contains `Bytes` and `Utf8Bytes` using the shared
strategy, alongside `Buffer`, `BytesMut`, `Utf8Buffer`, and `Utf8BytesMut`.
`smol-bytes/shared` exports the exact same immutable class objects; the
`smol-bytes/compact` entry point exports distinct compact classes. Every entry
point initializes iteration, disposal, and input validation independently.

Raw bytes use `fromUtf8()` and `decodeUtf8()`; they deliberately have no
string-like `fromString()` or raw `toString()` API. Neither raw nor UTF-8
public classes expose `fromStatic()`; UTF-8 classes retain
`fromString()` / `toString()` and make byte units explicit:
`byteLength()`, `charLength()`, `remainingBytes()`, `advanceBytes()`,
`splitToBytes()`, `splitOffBytes()`, `sliceBytes()`, and `truncateBytes()`.
Byte offsets must land at UTF-8 scalar boundaries. Numeric arguments are
checked before WebAssembly coercion, and malformed JavaScript UTF-16 strings
are rejected before encoding.

Byte conversions at the Wasm boundary return copies. `clone()` is available on
each public byte/string value class. Mutable `freezeShared()` / `freezeCompact()` drain their
source into the selected immutable strategy, while immutable `toMutable()`
returns an independent mutable value. JavaScript `unsplit()` snapshots and
appends `other` without consuming it; self-aliasing deterministically doubles
the value.

```typescript
import { Bytes, BytesMut, Utf8Bytes } from "smol-bytes";
import { Bytes as CompactBytes } from "smol-bytes/compact";

const raw = CompactBytes.fromBytes(new Uint8Array([1, 2, 3]));
console.assert(raw.isInline());
console.assert(raw.toBytes()[2] === 3);

const shared = Bytes.fromUtf8("hello");
console.assert(shared.decodeUtf8() === "hello");

const mutable = BytesMut.fromUtf8("ready");
const frozen = mutable.freezeShared();
console.assert(mutable.isEmpty() && frozen.decodeUtf8() === "ready");

const text = Utf8Bytes.fromString("café");
console.assert(text.byteLength() === 5);
console.assert(text.charLength() === 4);
console.assert(text.toString() === "café");
```

## Performance characteristics

Structural properties of the representations:

- Values of at most 62 bytes construct inline with no backing allocation.
- Every handle is 64 bytes; `Option<Bytes>` is the same size as `Bytes`.
- Cloning an inline value copies 64 bytes; cloning a heap-backed immutable
  value bumps a reference count; cloning `BytesMut`/`Utf8BytesMut` copies
  contents.
- Shared heap-backed conversions to and from `bytes::Bytes` reuse the
  backing allocation.
- Compact storage copies at most 62 bytes when inlining a shrinking view.

Run the benchmark suite when investigating a change:

```bash
cargo bench
cargo bench --bench clone
cargo bench --bench split_to
```

## Development

These commands match the main CI checks:

```bash
cargo fmt --all -- --check
cargo clippy --workspace --no-default-features --features std,alloc,serde,schemars,borsh,arbitrary,quickcheck --all-targets -- -D warnings
cargo test --workspace --no-default-features --features std,serde,schemars,borsh,arbitrary,quickcheck
cargo test --package smol-bytes --no-default-features --features alloc,quickcheck
cargo check --package smol-bytes --lib --no-default-features
cargo doc --package smol-bytes --no-deps
```

See [CONTRIBUTING.md](CONTRIBUTING.md) for contribution guidance.

## License

`smol-bytes` is available under either the MIT license or the Apache License,
Version 2.0, at your option. See [LICENSE-MIT](LICENSE-MIT) and
[LICENSE-APACHE](LICENSE-APACHE).

Copyright (c) 2026 Al Liu.

[Github-url]: https://github.com/al8n/smol-bytes/
[CI-url]: https://github.com/al8n/smol-bytes/actions/workflows/ci.yml
[doc-url]: https://docs.rs/smol-bytes
[crates-url]: https://crates.io/crates/smol-bytes
[codecov-url]: https://app.codecov.io/gh/al8n/smol-bytes/
[discord]: https://discord.gg/ujPyPY6ud9