Reading the bytes crate: four buffer types, two features, and the parts the README skips
Utilities for working with bytes
At a glance
- What is it?
- bytes gives Rust programs four buffer types and two traits, and its Cargo.toml says more about the build than its README says about the code. A close reading turns up a 1.57 MSRV that two features can move, a crate package that leaves out its own benchmarks, and a publishing checklist parked in manifest comments.
- Who is it for?
- Adopt bytes when your Rust code shuttles byte streams between readers and writers and you want buffer types with a documented feature surface instead of hand rolled ones, and stay away from it for string work or for any job that needs a mutable buffer everywhere. Verify first that your toolchain meets rust-version = 1.57, because switching on serde or extra-platforms hands the real floor to those two crates.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 12 days ago.
- What is it written in?
- Mainly Rust, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Star history
- tokio-rs/bytes2,266 ★ · +1
| Repository | 2026-10-14 | 2026-10-22 |
|---|---|---|
| tokio-rs/bytes | 2.3k | 2.3k |
1 Stars in the last 8 days, measured from the daily GitHub snapshot. Compare with other projects
Editorial analysis
What the four core types are for, and what stays undocumented
Any Rust service that reads or writes a stream ends up writing the same four steps by hand: fill a buffer, hand out a slice, refill it, split it, then let two workers hold different halves. bytes exists to collect those steps in one place. Its Cargo.toml calls it Types and traits for working with bytes, the README opens with A utility library for working with bytes, and the categories field claims network-programming and data-structures. That is the whole published positioning.
Adding it takes one line under [dependencies].
[dependencies]
bytes = "1"The first real code the README offers is an import rather than a program.
use bytes::{Bytes, BytesMut, Buf, BufMut};So the reader learns four names and nothing about them. When to reach for an immutable Bytes handle instead of a mutable BytesMut, and what Buf and BufMut buy you over indexing a slice yourself, is answered on docs.rs/bytes rather than here. A reader who wants the zero-copy story the keywords field promises has to leave the repository to find it.
no_std means switching off a feature that contains nothing
The manifest defaults to std, and writes the whole features table as two lines: default = ["std"] and std = []. A feature with an empty body gates nothing you can point at in this repository, so the switch exists for the build graph and for downstream crates rather than to strip out code. Turning it off is a matter of refusing the defaults.
[dependencies]
bytes = { version = "1", default-features = false }Platforms without atomic CAS need a second switch, and the README names thumbv6m as the case to think about. extra-platforms is the feature name, but what it pulls in is the portable-atomic crate at version 1.3 with default-features = false and features = ["require-cas"]. The comment sitting directly above that dependency line in Cargo.toml gives the honest reason, a better error message when the end user forgets the cfg or the feature. In other words, extra-platforms is a diagnostic wrapper around somebody else's crate, not a portability layer built here.
The same README section carries the caveat that matters: once extra-platforms is on, the MSRV depends on the MSRV of portable-atomic. The floor for this crate is then set by another project, and nothing in the repository spells out which version of that project applies.
A stated MSRV of 1.57 that two features can move
Two fields in Cargo.toml state the floor: rust-version = "1.57" and edition = "2021". Read on their own, that reads as a promise the whole crate keeps. The README takes it back in four lines, once for extra-platforms and once for serde, saying in both cases that the MSRV depends on the MSRV of the dependency you switched on. A manifest field that says 1.57 regardless of features is a claim about the default build, not about the build you will ship.
The practical consequence shows up as an unhelpful error message. Turn on the serde feature on a 1.57 toolchain and the compiler complains about serde, not about bytes, so the number that decides your build sits inside a dependency's own metadata. Run the oldest toolchain you intend to support against your lockfile before you trust the manifest, because that is the one check the project cannot do on your behalf.
Serde support pulled in through alloc, not std
Serialization support is optional and disabled by default, which is a strange default for a crate whose job is handing data to other crates. The dependency entry explains the shape of it: serde at version 1.0.60, marked optional, with default-features = false and features = ["alloc"]. The impls therefore sit on serde's allocator rather than on its standard library support, and one dependency line serves both an ordinary build and a no_std build.
[dependencies]
bytes = { version = "1", features = ["serde"] }Testing that path needs no runtime framework. serde_test 1.0 sits in the dev-dependencies, and loom 0.7 is wired only under [target.'cfg(loom)'.dev-dependencies], with a matching check-cfg entry for 'cfg(loom)' so the unexpected_cfgs lint stays quiet. How you switch that target on is not described anywhere in the README, so expect to read the manifest to work out whether those concurrency tests run in your checkout at all.
Nightly rustdoc flags versus the docs.rs manifest key
Documentation building is the one task this project hands to nightly. The reason is mechanical rather than stylistic: feature gates stay hidden from the generated pages unless a docsrs cfg is set.
RUSTDOCFLAGS="--cfg docsrs" cargo +nightly docTwo separate mechanisms push that same cfg. On your machine it is the RUSTDOCFLAGS environment variable in the command above. On the hosted documentation site it is package.metadata.docs.rs with rustdoc-args = ["--cfg", "docsrs"], declared in the manifest rather than in a script. Build the pages without the cfg and every gated item quietly disappears, which is a poor first look at a crate whose entire appeal is the set of types it exposes.
The published crate drops the benches directory
The crate that reaches the registry is decided by one field. include names CHANGELOG.md, LICENSE, README.md, SECURITY.md, Cargo.toml, src/**/*.rs, tests/**/*.rs and clippy.toml, while the repository top level also holds .github/, ci/ and benches/. None of those three directories ships, so the benchmark sources stay in git and never reach a downstream dependency, whereas a lint configuration file travels with every copy. That asymmetry is the sort of detail a skim over the file listing misses.
It also removes the evidence that could settle the performance question. The keywords field advertises zero-copy, and not one line of documentation in this repository says what that means for your code, which paths avoid a copy, and what the split costs you. No measurement is published. If shared buffers are the reason you are considering the crate, read the docs.rs pages and run your own workload.
The only alternative the manifest actually names
The one competitor shaped decision the repository lets you make is portable-atomic. You can depend on it yourself for your own atomic needs, or you can let bytes pull it in through extra-platforms with require-cas and default-features = false. The difference is control and cost: a direct dependency gives you that crate's own choice of defaults, while the feature route takes the pinned 1.3 line with features trimmed down, and the trimmed build is what produces the friendlier error when somebody forgets.
The second comparison is serde_test 1.0 as a dev-dependency, where the impls are checked against a test harness rather than against a serialization framework that runs in production code. Neither alternative is advertised anywhere in the README, and both are visible only in the manifest, which is the strongest argument for opening Cargo.toml before you decide.
Two lines of manifest comments carry the publishing checklist
Setup for a new tag lives where you would not look for it. The comment block at the top of Cargo.toml lists what has to happen before a version goes out, update the changelog file and create a "v1.x.y" git tag, and that one sentence covers the entire published procedure, since the crate is MIT licensed, the last push was on 2026-09-03, and the manifest carries version = "1.12.1" after v1.12.0 on 2026-06-18 and v1.11.1 on 2026-02-03.
The repository listing has no contributing guide, and the README's one line contribution note is about licensing rather than about process, so a contributor who does not open Cargo.toml first is working without the checklist. SECURITY.md ships inside the package even though nothing in the README points at it, which is the reverse arrangement: the file you would want during review is present, and the instructions you would want before tagging are not.
Editorial conclusion
Adopt bytes when your Rust code shuttles byte streams between readers and writers and you want buffer types with a documented feature surface instead of hand rolled ones, and stay away from it for string work or for any job that needs a mutable buffer everywhere. Verify first that your toolchain meets rust-version = 1.57, because switching on serde or extra-platforms hands the real floor to those two crates. Pin the feature set and build on the oldest compiler you claim to support, not on the one that happens to be installed.
Frequently asked questions
How do I add bytes to a Rust project?
Put bytes = "1" under [dependencies] in Cargo.toml, then import what you need with use bytes::{Bytes, BytesMut, Buf, BufMut};. That import line is the entire usage example the README gives.
Can bytes be used in a no_std crate?
Yes, by refusing the defaults with bytes = { version = "1", default-features = false }, which switches off the std feature that is on by default. Targets without atomic CAS, thumbv6m being the case the README names, also need the extra-platforms feature.
What Rust version does bytes require?
The manifest declares rust-version = "1.57" with edition = "2021". The README states that the effective MSRV depends on portable-atomic when extra-platforms is enabled and on serde when the serde feature is enabled.
Does bytes support serde serialization?
It does, through an optional feature that is disabled by default, enabled with features = ["serde"]. The serde dependency itself is declared with default-features = false and features = ["alloc"], so the impls do not require serde's standard library support.
How do I build the bytes documentation locally?
Run RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc, which needs a nightly toolchain. Without that cfg, feature gates are not shown on the generated pages; the hosted docs get the same cfg from rustdoc-args under package.metadata.docs.rs.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/tokio-rs-bytes)Star history chart for your README
[](https://hysenlabs.com/projects/tokio-rs-bytes)