pack_io/lib.rs
1//! # pack-io
2//!
3//! Compact binary wire format for Rust. Combines speed, schema evolution,
4//! and zero-copy deserialization under a single coherent contract.
5//!
6//! ## At a glance
7//!
8//! - **Tier 1** — [`encode`] and [`decode`]: one line each direction.
9//! - **Tier 2** —
10//! - [`Encoder`] / [`Decoder`] for in-memory buffers.
11//! - [`IoEncoder`] / [`IoDecoder`] for `std::io::Write` / `Read` streams
12//! (`std`-gated).
13//! - [`encode_into`] / [`decode_from`] convenience helpers over Read /
14//! Write.
15//! - **Tier 3** — implement [`Serialize`] / [`Deserialize`] on your own
16//! types. Both traits are generic over the [`Encode`] / [`Decode`]
17//! behaviour traits, so one impl works through every encoder / decoder
18//! the crate ships.
19//!
20//! ## Primitive support
21//!
22//! Integers (`u8` … `u128`, `i8` … `i128`, `usize` / `isize`), `bool`,
23//! `f32`, `f64`, `String` / `&str`, fixed-size arrays `[T; N]`, tuples of
24//! arity 1…12, `Option<T>`, `Result<T, E>`, and `()`.
25//!
26//! ## Collection support
27//!
28//! `Vec<T>` / `&[T]`, `BTreeMap<K, V>`, `BTreeSet<T>`, and (with the
29//! default `std` feature) `HashMap<K, V>` and `HashSet<T>`. **Hash-based
30//! collections encode in canonical key-sorted order** so that hashing,
31//! signing, or content-addressing the output is safe regardless of
32//! insertion order or hash randomisation.
33//!
34//! ## Stability
35//!
36//! The public API and wire format are frozen for the entire `1.x` line.
37//! Any `1.x` decoder reads any `1.x`-or-earlier encoding. See the
38//! normative spec at
39//! [`docs/WIRE_FORMAT.md`](https://github.com/jamesgober/pack-io/blob/main/docs/WIRE_FORMAT.md)
40//! and the frozen public surface at
41//! [`docs/API.md`](https://github.com/jamesgober/pack-io/blob/main/docs/API.md#frozen-public-surface).
42//!
43//! ## Quick start
44//!
45//! ```
46//! use pack_io::{encode, decode};
47//!
48//! let bytes = encode(&(7_u64, true, String::from("hello"))).unwrap();
49//! let back: (u64, bool, String) = decode(&bytes).unwrap();
50//! assert_eq!(back, (7, true, String::from("hello")));
51//! ```
52//!
53//! ## Invariants
54//!
55//! - **Round-trip integrity** — `decode(encode(v)) == v` for every
56//! supported type, under any input.
57//! - **Determinism** — the same value always produces the same bytes,
58//! regardless of insertion order, platform, or build flags.
59//! - **Safe decode** — no panic, no unbounded allocation, no read past
60//! the input, on any byte sequence.
61//! - **Wire-format stability** — any `1.x` decoder reads any
62//! `1.x`-or-earlier encoding.
63//!
64//! ## `no_std`
65//!
66//! `pack-io` is `no_std`-capable. The default build enables `std` for the
67//! [`std::error::Error`] impl, `HashMap` / `HashSet` integration, and the
68//! [`io`] module. Disable the default feature to compile against `core` +
69//! `alloc` only:
70//!
71//! ```toml
72//! pack-io = { version = "1", default-features = false }
73//! ```
74
75#![cfg_attr(not(feature = "std"), no_std)]
76#![cfg_attr(docsrs, feature(doc_cfg))]
77#![deny(missing_docs)]
78#![deny(unused_must_use)]
79#![deny(clippy::todo)]
80#![deny(clippy::unimplemented)]
81#![deny(clippy::print_stdout)]
82#![deny(clippy::print_stderr)]
83#![deny(clippy::dbg_macro)]
84#![deny(clippy::undocumented_unsafe_blocks)]
85#![forbid(unsafe_code)]
86
87extern crate alloc;
88
89#[cfg(feature = "std")]
90extern crate std;
91
92mod codec;
93mod error;
94mod impls;
95#[cfg(feature = "std")]
96pub mod io;
97mod traits;
98mod varint;
99mod view;
100
101pub use crate::codec::{Config, Decode, Decoder, Encode, Encoder, decode, encode, peek_version};
102pub use crate::error::{Result, SerialError};
103pub use crate::traits::{Deserialize, Serialize};
104pub use crate::view::{DeserializeView, decode_view};
105
106#[cfg(feature = "std")]
107pub use crate::io::{IoDecoder, IoEncoder, decode_from, encode_into};
108
109// Re-export the derive macros when the `derive` feature is on. Users write
110// `#[derive(pack_io::Serialize, pack_io::Deserialize, pack_io::DeserializeView)]`
111// and the proc-macro crate is the implementation detail.
112#[cfg(feature = "derive")]
113pub use pack_io_derive::{Deserialize, DeserializeView, Serialize};
114
115/// Semantic version of this crate, as declared in `Cargo.toml`.
116///
117/// # Examples
118///
119/// ```
120/// // VERSION mirrors Cargo.toml exactly, with no parsing.
121/// assert_eq!(pack_io::VERSION, env!("CARGO_PKG_VERSION"));
122/// ```
123pub const VERSION: &str = env!("CARGO_PKG_VERSION");
124
125#[cfg(test)]
126mod tests {
127 use super::*;
128
129 #[test]
130 fn version_matches_cargo_manifest() {
131 assert_eq!(VERSION, env!("CARGO_PKG_VERSION"));
132 }
133
134 // `alloc::string::String` rather than the prelude `String`, so this
135 // test compiles under `cargo test --no-default-features` (no `std`).
136 #[test]
137 fn tier_one_encode_decode_round_trips_a_tuple() {
138 use alloc::string::String;
139 let bytes = encode(&(1_u64, true, String::from("hello"))).expect("encode");
140 let back: (u64, bool, String) = decode(&bytes).expect("decode");
141 assert_eq!(back, (1, true, String::from("hello")));
142 }
143}