{"id":50370813,"url":"https://github.com/jamesgober/pack-io","last_synced_at":"2026-06-05T05:00:51.872Z","repository":{"id":361324859,"uuid":"1254035979","full_name":"jamesgober/pack-io","owner":"jamesgober","description":"Compact binary wire format with schema evolution and zero-copy deserialization for Rust.","archived":false,"fork":false,"pushed_at":"2026-05-30T06:49:39.000Z","size":47,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-05-30T07:19:18.912Z","etag":null,"topics":["binary","codec","reps","rust","schema-evolution","serialization","zero-copy"],"latest_commit_sha":null,"homepage":null,"language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jamesgober.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE-APACHE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-30T04:21:29.000Z","updated_at":"2026-05-30T06:47:34.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/jamesgober/pack-io","commit_stats":null,"previous_names":["jamesgober/pack-io"],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/jamesgober/pack-io","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jamesgober%2Fpack-io","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jamesgober%2Fpack-io/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jamesgober%2Fpack-io/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jamesgober%2Fpack-io/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jamesgober","download_url":"https://codeload.github.com/jamesgober/pack-io/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jamesgober%2Fpack-io/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33930311,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-05T02:00:06.157Z","response_time":120,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["binary","codec","reps","rust","schema-evolution","serialization","zero-copy"],"created_at":"2026-05-30T07:01:26.830Z","updated_at":"2026-06-05T05:00:51.843Z","avatar_url":"https://github.com/jamesgober.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003ch1 align=\"center\"\u003e\n    \u003cimg width=\"99\" alt=\"Rust logo\" src=\"https://raw.githubusercontent.com/jamesgober/rust-collection/72baabd71f00e14aa9184efcb16fa3deddda3a0a/assets/rust-logo.svg\"\u003e\n    \u003cbr\u003e\n    \u003cb\u003epack-io\u003c/b\u003e\n    \u003cbr\u003e\n    \u003csub\u003e\n        \u003csup\u003eCOMPACT BINARY WIRE FORMAT\u003c/sup\u003e\n    \u003c/sub\u003e\n\u003c/h1\u003e\n\n\u003cdiv align=\"center\"\u003e\n    \u003ca href=\"https://crates.io/crates/pack-io\"\u003e\u003cimg alt=\"Crates.io\" src=\"https://img.shields.io/crates/v/pack-io\"\u003e\u003c/a\u003e\n    \u003ca href=\"https://crates.io/crates/pack-io\" alt=\"Download pack-io\"\u003e\u003cimg alt=\"Crates.io Downloads\" src=\"https://img.shields.io/crates/d/pack-io?color=%230099ff\"\u003e\u003c/a\u003e\n    \u003ca href=\"https://docs.rs/pack-io\" title=\"pack-io Documentation\"\u003e\u003cimg alt=\"docs.rs\" src=\"https://img.shields.io/docsrs/pack-io\"\u003e\u003c/a\u003e\n    \u003ca href=\"https://github.com/jamesgober/pack-io/actions\"\u003e\u003cimg alt=\"GitHub CI\" src=\"https://github.com/jamesgober/pack-io/actions/workflows/ci.yml/badge.svg\"\u003e\u003c/a\u003e\n    \u003ca href=\"https://github.com/rust-lang/rfcs/blob/master/text/2495-min-rust-version.md\" title=\"MSRV\"\u003e\u003cimg alt=\"MSRV\" src=\"https://img.shields.io/badge/MSRV-1.85%2B-blue\"\u003e\u003c/a\u003e\n\u003c/div\u003e\n\n\u003cbr\u003e\n\n\u003cdiv align=\"left\"\u003e\n    \u003cp\u003e\n        \u003cstrong\u003epack-io\u003c/strong\u003e is a \u003cb\u003ecompact binary wire format\u003c/b\u003e for Rust built around three properties existing serialization crates split across three separate libraries: \u003cb\u003espeed\u003c/b\u003e, \u003cb\u003eschema evolution\u003c/b\u003e, and \u003cb\u003ezero-copy deserialization\u003c/b\u003e. It is engineered as the serialization substrate underneath \u003ca href=\"https://github.com/jamesgober/network-protocol\"\u003e\u003ccode\u003enetwork-protocol\u003c/code\u003e\u003c/a\u003e, \u003ca href=\"https://github.com/jamesgober/wire-codec\"\u003e\u003ccode\u003ewire-codec\u003c/code\u003e\u003c/a\u003e, and Hive DB - so it has to be fast, deterministic, schema-aware, and safe under untrusted input from day one.\n    \u003c/p\u003e\n    \u003cp\u003e\n        Existing crates each cover part of the problem. \u003ccode\u003ebincode\u003c/code\u003e is fast but has no schema evolution. \u003ccode\u003erkyv\u003c/code\u003e gives zero-copy but requires unsafe alignment discipline at the use site. \u003ccode\u003epostcard\u003c/code\u003e is embedded-focused and lean. None of them own all three properties as a single coherent contract. \u003ccode\u003epack-io\u003c/code\u003e does, behind a small, predictable wire format you can read with a spec instead of with the source code.\n    \u003c/p\u003e\n    \u003cp\u003e\n        The common-case API is one line - \u003ccode\u003eencode(\u0026amp;value)\u003c/code\u003e and \u003ccode\u003edecode::\u0026lt;T\u0026gt;(\u0026amp;bytes)\u003c/code\u003e - and that path is the fast path. Schema versions, evolution helpers, and the zero-copy view API live behind feature flags so the default build stays small.\n    \u003c/p\u003e\n    \u003cbr\u003e\n    \u003chr\u003e\n    \u003cp\u003e\n        \u003cstrong\u003eMSRV is 1.85+\u003c/strong\u003e (Rust 2024 edition). \u003ccode\u003eno_std\u003c/code\u003e-capable. Deterministic encoding. No \u003ccode\u003eunsafe\u003c/code\u003e on the safe-decoding path.\n    \u003c/p\u003e\n    \u003cblockquote\u003e\n        \u003cstrong\u003eStatus: beta as of v0.9.0; API frozen since v0.7.0.\u003c/strong\u003e The public API listed in \u003ca href=\"./docs/API.md#frozen-public-surface\"\u003e\u003ccode\u003edocs/API.md\u003c/code\u003e\u003c/a\u003e is the surface that ships in v1.0; source-breaking changes are deferred to v2.0. The wire format has been frozen since v0.3.0 (spec version 1.2). v0.9.x is a \u003cstrong\u003ebug-fixes-only\u003c/strong\u003e window — broader testing, the v1.0 performance baseline (\u003ca href=\"./docs/PERFORMANCE_BASELINE.md\"\u003e\u003ccode\u003edocs/PERFORMANCE_BASELINE.md\u003c/code\u003e\u003c/a\u003e), and any consumer-surfaced bugs. RC enters at v0.9.5+ with critical fixes + doc polish only. See \u003ca href=\"./CHANGELOG.md\"\u003e\u003ccode\u003eCHANGELOG.md\u003c/code\u003e\u003c/a\u003e for detail.\n    \u003c/blockquote\u003e\n\u003c/div\u003e\n\n\n\u003chr\u003e\n\u003cbr\u003e\n\n## Why pack-io\n\nExisting crates each cover a slice of the problem; none of them own all three properties together.\n\n| Crate       | Speed | Schema evolution | Zero-copy decode | Wire-format spec |\n|-------------|:-----:|:----------------:|:----------------:|:----------------:|\n| `bincode`   |   ✓   |        —         |        —         |        —         |\n| `rkyv`      |   ✓   |        —         |        ✓ *       |        —         |\n| `postcard`  |   ✓   |        —         |        —         |        ✓         |\n| **`pack-io`** | **✓** |       **✓**      |       **✓**      |       **✓**      |\n\n\u003csub\u003e* `rkyv` requires alignment discipline at every use site; the cost is paid by the caller, not the codec.\u003c/sub\u003e\n\nThe 1.0 contract is the same wire format on every supported platform, the same bytes for the same value every time, and no panic / no unbounded allocation on any input.\n\n### Speed claim, with numbers\n\nThe \"Speed ✓\" claim is backed by [`docs/PERFORMANCE.md`](./docs/PERFORMANCE.md) (Windows x86_64, Rust stable, release build, reproducible via `cargo bench --bench comparative --features derive`):\n\n| Workload | pack-io | bincode | postcard | rkyv | Result |\n|---|---:|---:|---:|---:|---|\n| owned struct encode | **38 ns** | 40 ns | 232 ns | 114 ns | **pack-io fastest** |\n| 64-byte `String` round-trip | **46 ns** | 52 ns | 87 ns | — | **pack-io fastest** |\n| zero-copy view of 64-byte `\u0026str` | **5.1 ns** | — | — | — | uncontested |\n| `Vec\u003cu8\u003e` 4 KiB decode | 68 ns | **64 ns** | 1,800 ns | — | within noise of bincode |\n| `u64` round-trip | 22 ns | **21 ns** | 25 ns | — | within 5 % of bincode |\n| owned struct decode | 173 ns | **165 ns** | 285 ns | 153 ns | within noise of bincode, ~tied with rkyv |\n| zero-copy view of struct | 35 ns | — | — | **12 ns** | rkyv 3× faster (intentional — rkyv reads raw memory, pack-io walks varints by spec) |\n\npack-io is the fastest of the four on **encode**, owning **String** decode, and zero-copy `\u0026str` view (the last is uncontested — neither bincode nor postcard has a zero-copy story). On the other four workloads we are tied or within 5 % of the leader. The only meaningful gap is vs rkyv's archived path, which trades the wire-format spec for raw-memory access — a trade pack-io declines on purpose. Full numbers + methodology + honest per-row analysis live in [`docs/PERFORMANCE.md`](./docs/PERFORMANCE.md).\n\n\u003cbr\u003e\n\u003chr\u003e\n\u003cbr\u003e\n\n## What it does\n\n- **Compact binary encoding** of Rust values into a small, predictable wire format\n- **Schema-versioned messages** - producer and consumer can be at different revisions and still interoperate\n- **Zero-copy deserialization** for `\u0026[u8]` / `\u0026str` / length-prefixed slices when the input lives long enough\n- **Deterministic output** - the same value always produces the same bytes (canonical encoding)\n- **Safe under untrusted input** - bounded allocation, length-prefix validation, no panics on malformed bytes\n- **Runtime-agnostic** - synchronous codec, usable from any context\n\n\u003cbr\u003e\n\n## Features\n\n- **Compact** — small, fixed-overhead encoding; varint integers; length-prefixed byte slices\n- **Schema evolution** — additive field changes, optional fields, version negotiation\n- **Zero-copy decode** — view types that borrow from the input buffer where possible\n- **Deterministic** — canonical encoding for hashing, signing, content-addressing\n- **Safe defaults** — bounded allocation, validated lengths, no panics on bad input\n- **`no_std`-capable** — embedded and constrained environments\n- **Derive macro** — `#[derive(Serialize, Deserialize)]` for any struct or enum (feature `derive`)\n- **Optional `serde` interop** — read and write `serde` types via a thin adapter (feature `serde`)\n\n\u003chr\u003e\n\u003cbr\u003e\n\n## Roadmap snapshot\n\n| Version | Scope | Status |\n|---------|-------|:------:|\n| `0.1.0` | Scaffold: structure, CI, lints, quality gates | ✅ shipped |\n| `0.2.0` | Foundation: `encode` / `decode`, primitive types, `Serialize` / `Deserialize`, round-trip + determinism + adversarial-decode proptests | ✅ shipped |\n| `0.3.0` | Wire-format freeze, collections (`Vec`, `HashMap`, `BTreeMap`, sets), streaming over `Read` / `Write`, normative [`docs/WIRE_FORMAT.md`](./docs/WIRE_FORMAT.md) | ✅ shipped |\n| `0.4.0` | `View\u003cT\u003e` zero-copy decode + `derive` macro + enum wire format | ✅ shipped |\n| `0.5.0` | Schema evolution attributes (`#[pack_io(version = N)]` / `since` / `deprecated`) + `peek_version` + **feature freeze** | ✅ shipped |\n| `0.6.0` | Optimisation pass: `Vec\u003cu8\u003e` decode 38× faster (now beats bincode), comparative benchmarks vs `bincode` / `postcard` / `rkyv` documented | ✅ shipped |\n| `0.7.0` | Hardening: 8-target `cargo-fuzz` harness in CI, cross-platform byte-equivalence golden vectors, hostile-input sweep, **public API frozen** | ✅ shipped |\n| `0.8.0` | **Alpha**: integration window open for first real consumers (`network-protocol`, `wire-codec`, Hive DB). Consumer-shape integration tests + examples. Point releases (0.8.x) track surfaced bugs. | ✅ shipped |\n| `0.9.0` | **Beta**: bug-fixes-only window; broader testing (13 fuzz targets, 60 s/target in CI); v1.0 performance baseline captured in [`docs/PERFORMANCE_BASELINE.md`](./docs/PERFORMANCE_BASELINE.md). | ✅ shipped |\n| `0.9.5+` | RC: critical fixes + doc polish only | planned |\n| `1.0.0` | Wire-format + API freeze | planned |\n\nThe roadmap is followed strictly; phases are not skipped. Per-phase exit criteria are tracked internally and surfaced in each release note.\n\n\u003chr\u003e\n\u003cbr\u003e\n\n## Installation\n\n```toml\n[dependencies]\npack-io = \"0.9\"\n\n# With derive macro (planned for 0.4+):\npack-io = { version = \"0.9\", features = [\"derive\"] }\n\n# no_std build:\npack-io = { version = \"0.9\", default-features = false }\n```\n\n\u003cbr\u003e\n\n## API surface (v0.3.0)\n\nThe full Tier-1 / Tier-2 / Tier-3 surface is live, with the wire format frozen for the `1.x` line. See [`docs/API.md`](./docs/API.md) for the complete reference and [`docs/WIRE_FORMAT.md`](./docs/WIRE_FORMAT.md) for the normative byte-level spec.\n\n### Tier 1 — the lazy path\n\n```rust\nuse pack_io::{encode, decode};\n\nlet bytes: Vec\u003cu8\u003e = encode(\u0026(7_u64, true, String::from(\"hello\"))).unwrap();\nlet back: (u64, bool, String) = decode(\u0026bytes).unwrap();\nassert_eq!(back, (7, true, String::from(\"hello\")));\n```\n\n### Tier 2a — the in-memory `Encoder` / `Decoder`\n\nRe-use a single `Vec\u003cu8\u003e` across many encodes; read several values from one buffer. Configuration (`Config::max_alloc`) is validated at construction time, not on every operation.\n\n```rust\nuse pack_io::{Encoder, Decoder, Config};\n\nlet mut enc = Encoder::new();\nenc.write(\u00267_u64).unwrap();\nenc.write(\u0026\"hello\").unwrap();\nlet bytes = enc.into_inner();\n\nlet cfg = Config::new().with_max_alloc(16 * 1024);\nlet mut dec = Decoder::with_config(\u0026bytes, cfg).unwrap();\nlet n: u64 = dec.read().unwrap();\nlet s: String = dec.read().unwrap();\nassert_eq!((n, s.as_str()), (7, \"hello\"));\n```\n\n### Tier 2b — the streaming `IoEncoder\u003cW\u003e` / `IoDecoder\u003cR\u003e` (new in v0.3)\n\nWrite directly into any `std::io::Write`, read from any `std::io::Read`. Gated on the default `std` feature.\n\n```rust\nuse pack_io::{IoEncoder, IoDecoder, encode_into, decode_from};\nuse std::io::Cursor;\n\n// Single-shot helpers (encode_into / decode_from) over any Read / Write:\nlet mut sink: Vec\u003cu8\u003e = Vec::new();\nencode_into(\u0026(42_u64, \"hello\"), \u0026mut sink).unwrap();\nlet back: (u64, String) = decode_from(\u0026mut Cursor::new(sink)).unwrap();\nassert_eq!(back, (42, \"hello\".to_string()));\n\n// Or hold an encoder / decoder for multi-value streams:\nlet mut buf: Vec\u003cu8\u003e = Vec::new();\n{\n    let mut enc = IoEncoder::new(\u0026mut buf);\n    enc.write(\u00261_u64).unwrap();\n    enc.write(\u00262_u64).unwrap();\n}\nlet mut dec = IoDecoder::new(Cursor::new(buf));\nlet a: u64 = dec.read().unwrap();\nlet b: u64 = dec.read().unwrap();\nassert_eq!((a, b), (1, 2));\n```\n\n### Tier 3 — derive on your own types\n\n`#[derive(Serialize, Deserialize)]` writes the boilerplate. Works on every struct shape (named, tuple, unit), every enum variant shape, and on generic types.\n\n```rust\nuse pack_io::{Serialize, Deserialize};\n\n#[derive(Serialize, Deserialize)]\nstruct Account {\n    id: u64,\n    handle: String,\n    flags: Vec\u003cString\u003e,\n    active: bool,\n}\n\n#[derive(Serialize, Deserialize)]\nenum Event {\n    Heartbeat,\n    Login { user: u64, ip: String },\n    Error(u32, String),\n}\n```\n\nEnums encode as `varint(variant_index) ++ fields` — variant indices are source-declaration order, so **append new variants to the end** to keep the wire shape backward-compatible.\n\n### Schema evolution — append-only forward and backward compatibility\n\nAdd `#[pack_io(version = N)]` to your type and `#[pack_io(since = N)]` to additive fields. Old encoders write what they know; new decoders read what's there and `Default::default()` the rest. New encoders write everything; old decoders read what they know and skip the trailing bytes inside the length-framed body.\n\n```rust\nuse pack_io::{Serialize, Deserialize, encode, decode, peek_version};\n\n#[derive(Serialize, Deserialize)]\n#[pack_io(version = 1)]\nstruct MessageV1 { id: u64, text: String }\n\n#[derive(Serialize, Deserialize)]\n#[pack_io(version = 2)]\nstruct MessageV2 {\n    id: u64,\n    text: String,\n    #[pack_io(since = 2)]\n    timestamp: Option\u003cu64\u003e,\n}\n\nlet bytes = encode(\u0026MessageV1 { id: 7, text: \"hello\".into() }).unwrap();\nassert_eq!(peek_version(\u0026bytes).unwrap(), 1);\n\n// v2 reads v1 cleanly: `timestamp` defaults to None.\nlet upgraded: MessageV2 = decode(\u0026bytes).unwrap();\nassert_eq!(upgraded.timestamp, None);\n\n// And v1 reads v2 cleanly: the trailing timestamp bytes are skipped.\nlet bytes = encode(\u0026MessageV2 {\n    id: 7,\n    text: \"hello\".into(),\n    timestamp: Some(42),\n}).unwrap();\nlet downgraded: MessageV1 = decode(\u0026bytes).unwrap();\nassert_eq!(downgraded.id, 7);\n```\n\nVersioned structs wrap their body in a `varint(version) ++ varint(body_len) ++ body` frame; non-versioned structs keep the v0.4 plain encoding. The choice is per-type, opt-in via the attribute. Use `#[pack_io(deprecated = N)]` to retire a field — encoders at version ≥ N drop it; decoders reading payloads at version \u003c N still read it. Full normative spec: [`docs/WIRE_FORMAT.md §3.8`](./docs/WIRE_FORMAT.md#38-versioned-structs).\n\nPulls in the `schema` feature: `pack-io = { version = \"0.5\", features = [\"schema\"] }`.\n\n### Tier 3 — zero-copy `View\u003cT\u003e`\n\n`#[derive(DeserializeView)]` plus the [`decode_view`] free function give you a parallel \"borrowed\" decode path. `\u0026'a str` and `\u0026'a [u8]` fields point directly into the input buffer — no per-field allocation, the borrow checker enforces the lifetime.\n\n```rust\nuse pack_io::{Serialize, DeserializeView, decode_view, encode};\n\n#[derive(Serialize)]\nstruct OwnedMsg { id: u64, text: String, payload: Vec\u003cu8\u003e }\n\n#[derive(DeserializeView)]\nstruct ViewMsg\u003c'a\u003e { id: u64, text: \u0026'a str, payload: \u0026'a [u8] }\n\nlet bytes = encode(\u0026OwnedMsg {\n    id: 7,\n    text: \"borrowed\".into(),\n    payload: vec![1, 2, 3],\n}).unwrap();\n\nlet view: ViewMsg\u003c'_\u003e = decode_view(\u0026bytes).unwrap();\nassert_eq!(view.text, \"borrowed\");  // points into `bytes`\n```\n\nOn a representative borrow-heavy record (`u64 + String + Vec\u003cu8\u003e + Vec\u003cString\u003e + Vec\u003cu8\u003e`), local Criterion microbenchmarks show:\n\n| Path | Time | vs owning |\n|---|---:|---:|\n| `decode::\u003cOwnedRecord\u003e` | 270 ns | 1.0× |\n| `decode_view::\u003cViewRecord\u003c'_\u003e\u003e` | **38 ns** | **~7.2× faster** |\n\nFor a 64-byte `String`:\n\n| Path | Time | vs owning |\n|---|---:|---:|\n| owning string decode (round-trip) | 77 ns | 1.0× |\n| `decode_view::\u003c\u0026str\u003e` (round-trip) | **5.6 ns** | **~14× faster** |\n\nReproduce with `cargo bench --bench codec_bench --features derive`.\n\n### If you need to hand-roll: the [`Serialize`] / [`Deserialize`] traits\n\nBoth are generic over the `Encode` / `Decode` behaviour traits — one impl works through every encoder flavour the crate ships (in-memory **and** streaming).\n\n```rust\nuse pack_io::{Decode, Deserialize, Encode, Result, Serialize};\n\nstruct Point { x: i32, y: i32 }\n\nimpl Serialize for Point {\n    fn serialize\u003cE: Encode + ?Sized\u003e(\u0026self, enc: \u0026mut E) -\u003e Result\u003c()\u003e {\n        self.x.serialize(enc)?;\n        self.y.serialize(enc)\n    }\n}\n\nimpl Deserialize for Point {\n    fn deserialize\u003cD: Decode + ?Sized\u003e(dec: \u0026mut D) -\u003e Result\u003cSelf\u003e {\n        Ok(Point {\n            x: i32::deserialize(dec)?,\n            y: i32::deserialize(dec)?,\n        })\n    }\n}\n```\n\n### Types supported in v0.4.0\n\n| Group | Types |\n|---|---|\n| Unsigned integers | `u8`, `u16`, `u32`, `u64`, `u128`, `usize` |\n| Signed integers | `i8`, `i16`, `i32`, `i64`, `i128`, `isize` |\n| Floats | `f32`, `f64` |\n| Bool / unit | `bool`, `()` |\n| Strings | `String`, `\u0026str` (encode + view) |\n| Bytes | `Vec\u003cu8\u003e`, `\u0026[u8]` (encode + view) |\n| Sequences | `Vec\u003cT\u003e`, `\u0026[T]` (encode), `[T; N]` |\n| Tuples | arity 1 through 12 |\n| Sums | `Option\u003cT\u003e`, `Result\u003cT, E\u003e` |\n| Maps | `BTreeMap\u003cK, V\u003e`, `HashMap\u003cK, V\u003e` *(std)* |\n| Sets | `BTreeSet\u003cT\u003e`, `HashSet\u003cT\u003e` *(std)* |\n| References | `\u0026T` where `T: Serialize` (encode) |\n| User types | any struct / enum with `#[derive(Serialize, Deserialize)]` *(derive feature)* |\n| Zero-copy types | any struct with `#[derive(DeserializeView)]` *(derive feature)* |\n\n### Canonical map / set encoding (the determinism contract)\n\nHash-based collections (`HashMap`, `HashSet`) are encoded with entries sorted lexicographically by their **encoded key bytes**. A `HashMap` and a `BTreeMap` holding the same logical data therefore encode to **identical bytes**, regardless of insertion order or build-flag-dependent hash randomisation. This is the load-bearing property for hashing, signing, and content-addressing pack-io payloads. Full normative spec: [`docs/WIRE_FORMAT.md` §4](./docs/WIRE_FORMAT.md#4-maps-and-sets).\n\n\u003cbr\u003e\n\n## Invariants (held from v0.1.0)\n\n- **Round-trip integrity** — `decode(encode(v)) == v` for every supported type, under any input.\n- **Determinism** — the same value always produces the same bytes; no map-iteration-order leaks, no time-dependence, no platform-dependence.\n- **Safe decode** — no panic, no unbounded allocation, no read past input, on any byte sequence.\n- **Wire-format stability** — frozen at `1.0`; any `1.x` decoder reads any `1.x`-or-earlier encoding.\n\nThese invariants hold for every release in the `0.x` series. As of `0.3.0` they are enforced by 177 tests: round-trip + determinism property tests for every primitive **and** every collection (including the load-bearing \"HashMap and BTreeMap encode identically\" property), plus adversarial-decode harnesses that fuzz every public decode entry point with random bytes, truncations, and hostile length prefixes. The wire format itself is frozen for the `1.x` line as of this release. A `cargo-fuzz` harness lands in `0.7`.\n\n\u003cbr\u003e\n\n## Testing\n\n```bash\n# Stable + MSRV (1.85) on Linux / macOS / Windows, full feature matrix\ncargo fmt --all -- --check\ncargo clippy --all-targets --all-features -- -D warnings\ncargo test --all-features\ncargo test --no-default-features\nRUSTDOCFLAGS=\"-D warnings\" cargo doc --no-deps --all-features\n\n# Supply chain\ncargo audit\ncargo deny check\n\n# Concurrency model checking (decoders are stateless; loom coverage is light-touch)\nRUSTFLAGS=\"--cfg loom\" cargo test --test loom_codec\n\n# Microbenchmarks\ncargo bench --bench codec_bench\n```\n\n\u003cbr\u003e\n\n## Examples\n\nEach example is self-contained and runs against the published API of the version it was added in.\n\n```bash\ncargo run --example basic_roundtrip --release                        # Tier-1 encode/decode of a tuple\ncargo run --example primitive_tour --release                         # one encoded value per primitive type\ncargo run --example reuse_buffer --release                           # Tier-2 Encoder + multi-value Decoder\ncargo run --example collections_tour --release                       # Vec / HashMap / BTreeMap / sets, canonical encoding\ncargo run --example streaming_io --release                           # IoEncoder / IoDecoder to a file\ncargo run --example derive_intro --features derive --release         # #[derive(Serialize, Deserialize)] on structs + enums\ncargo run --example view_zero_copy --features derive --release       # #[derive(DeserializeView)] borrows from the buffer\ncargo run --example schema_evolution --features schema --release     # v1 \u003c-\u003e v2 cross-version decode walkthrough\ncargo run --example protocol_handshake --features schema --release   # all four version combinations of a real handshake\ncargo run --example event_log --features derive --release             # WAL-style multi-event log written to a tempfile and replayed\n```\n\n\u003chr\u003e\n\u003cbr\u003e\n\n## Cross-Platform Support\n\n**Tier 1 Support:**\n- ✅ Linux (x86_64, aarch64)\n- ✅ macOS (x86_64, Apple Silicon)\n- ✅ Windows (x86_64)\n\nEncoding is byte-deterministic across all three; the CI matrix runs every target on stable and MSRV. Platform-specific behaviour is forbidden in the codec — there is no `#[cfg(target_os = …)]` branch on the encode or decode path.\n\n\u003cbr\u003e\n\n## Where It Fits\n\n`pack-io` is the serialization substrate under [`network-protocol`](https://github.com/jamesgober/network-protocol), [`wire-codec`](https://github.com/jamesgober/wire-codec), and Hive DB. It is consumed by [`raft-io`](https://github.com/jamesgober/raft-io) for log entries and by `event-stream` (when it lands) for message framing. It stays foreign-compatible: it works on its own without any other crate in the family.\n\n\u003cbr\u003e\n\n## Contributing\n\nBefore opening a PR, the full local checklist must pass:\n\n```bash\ncargo fmt --all -- --check\ncargo clippy --all-targets --all-features -- -D warnings\ncargo test --all-features\nRUSTDOCFLAGS=\"-D warnings\" cargo doc --no-deps --all-features\ncargo deny check\ncargo audit\n```\n\nAny change touching the wire format requires a `proptest` round-trip and a determinism test in the same commit. Wire-format-breaking changes are not accepted after `0.3` without an accompanying migration note in [`CHANGELOG.md`](./CHANGELOG.md).\n\n\u003cbr\u003e\n\n\u003chr\u003e\n\u003cbr\u003e\n\n\u003c!-- LICENSE\n############################################# --\u003e\n\u003cdiv id=\"license\"\u003e\n    \u003ch2\u003eLicense\u003c/h2\u003e\n    \u003cp\u003eLicensed under either of\u003c/p\u003e\n    \u003cul\u003e\n        \u003cli\u003e\u003cb\u003eApache License, Version 2.0\u003c/b\u003e — see \u003ca href=\"./LICENSE-APACHE\"\u003eLICENSE-APACHE\u003c/a\u003e (\u003ca href=\"http://www.apache.org/licenses/LICENSE-2.0\" target=\"_blank\"\u003ehttp://www.apache.org/licenses/LICENSE-2.0\u003c/a\u003e)\u003c/li\u003e\n        \u003cli\u003e\u003cb\u003eMIT License\u003c/b\u003e — see \u003ca href=\"./LICENSE-MIT\"\u003eLICENSE-MIT\u003c/a\u003e (\u003ca href=\"http://opensource.org/licenses/MIT\" target=\"_blank\"\u003ehttp://opensource.org/licenses/MIT\u003c/a\u003e)\u003c/li\u003e\n    \u003c/ul\u003e\n    \u003cp\u003eat your option. Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.\u003c/p\u003e\n\u003c/div\u003e\n\n\u003c!-- FOOT COPYRIGHT\n################################################# --\u003e\n\u003cdiv align=\"center\"\u003e\n  \u003ch2\u003e\u003c/h2\u003e\n  \u003csup\u003eCOPYRIGHT \u003csmall\u003e\u0026copy;\u003c/small\u003e 2026 \u003cstrong\u003eJAMES GOBER.\u003c/strong\u003e\u003c/sup\u003e\n\u003c/div\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjamesgober%2Fpack-io","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjamesgober%2Fpack-io","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjamesgober%2Fpack-io/lists"}