na_nbt
A high-performance NBT (Named Binary Tag) library for Rust with zero-copy parsing, full mutation support and serde integration.
⚠️ Note: This crate is under active development. APIs may change between versions. Issues and contributions are welcome!
Features
- Zero-copy parsing - Read NBT data without allocating memory for values
- Full mutation - Create and modify NBT structures with an owned representation
- Endianness support - Convert between BigEndian and LittleEndian on read or write
- Generic traits - Write code that works with any value type
- Serde integration - Serialize/deserialize Rust types directly to/from NBT (optional)
- Shared values - Thread-safe
Arc-based values withbytescrate (optional)
Installation
[]
= "0.1"
Optional Features
Both serde and shared features are enabled by default. To use without optional dependencies:
[]
= { = "0.1", = false }
# Or enable only what you need:
= { = "0.1", = false, = ["serde"] }
| Feature | Description | Dependencies |
|---|---|---|
serde |
Serialize/deserialize Rust types to/from NBT | serde |
shared |
SharedValue with Arc ownership |
bytes |
Todo
- More convenient APIs
- Benchmarks
- Tests and fuzzing
- MSRV testing
-
no_stdsupport - Older Rust support
Quick Start
use read_borrowed;
use BigEndian;
let data = ;
let doc = .unwrap;
let root = doc.root;
if let Some = root.get
Two Parsing Modes
| Mode | Function | Type | Use Case |
|---|---|---|---|
| Zero-copy (borrowed) | read_borrowed |
BorrowedValue |
Fast reads, data lives on stack/slice |
| Zero-copy (shared) | read_shared |
SharedValue |
Pass values across threads |
| Owned | read_owned |
OwnedValue |
Need to modify or outlive source data |
Zero-Copy Mode (Borrowed)
Parses NBT without copying data. Values reference the original byte slice directly.
use read_borrowed;
use BigEndian;
let data: & = &; // Empty compound
let doc = .unwrap;
let root = doc.root; // Zero-copy reference into `data`
Zero-Copy Mode (Shared)
Like borrowed mode, but wraps data in Arc for shared ownership. Values are
Clone, Send, Sync, and 'static - perfect for multi-threaded scenarios.
use read_shared;
use Bytes;
use BigEndian;
let data = from_static;
let root = .unwrap;
// Can clone and send to other threads
let cloned = root.clone;
spawn.join.unwrap;
Owned Mode
Parses NBT into an owned structure that can be modified.
use ;
use ;
let data: & = &;
// Convert from BigEndian source to LittleEndian storage
let mut root: = .unwrap;
if let Compound = root
Writing Generic Code
Trait Hierarchy
ScopedReadableValue (all types)
▲
│
┌───────┴───────┐
│ │
ReadableValue ScopedWritableValue
(immutable) (scoped mutation)
▲
│
WritableValue
(full mutation)
ScopedReadableValue- Base trait implemented by all value typesReadableValue- ExtendsScopedReadableValuewith document-lifetime referencesScopedWritableValue- ExtendsScopedReadableValuewith scoped mutationWritableValue- ExtendsScopedWritableValuewith full mutable references
Scoped vs Unscoped Methods
The "scoped" suffix indicates bounded lifetime and indirection:
-
Unscoped methods (e.g.,
get(),as_byte_array()) return references to data already stored in the value type with document lifetime'doc- can be stored independently. -
Scoped methods (e.g.,
get_scoped(),as_byte_array_scoped()) construct new view types on demand with borrow lifetime'a- necessary for types likeOwnedValuethat don't directly store container types.
When to use which:
- Use scoped methods when writing generic code that works with all value types
- Use unscoped methods when you need direct access to stored fields with longer lifetime
| Trait | Capability | Implemented By |
|---|---|---|
ScopedReadableValue |
Read primitives, iterate (scoped) | All value types |
ReadableValue |
+ Document-lifetime references | BorrowedValue, SharedValue, ImmutableValue |
ScopedWritableValue |
+ Mutation (scoped) | OwnedValue, MutableValue |
WritableValue |
+ Mutable references to containers | MutableValue |
use ;
Type Overview
Zero-Copy Types
| Type | Description |
|---|---|
ReadonlyValue |
Underlying zero-copy value |
BorrowedValue |
Type alias for borrowed data |
SharedValue |
Type alias for Arc-wrapped data |
Owned Types
| Type | Description |
|---|---|
OwnedValue |
Fully owned, mutable NBT value |
MutableValue |
Mutable view into an OwnedValue |
ImmutableValue |
Immutable view into an OwnedValue |
Feature Comparison
| Feature | BorrowedValue |
OwnedValue |
|---|---|---|
| Zero-copy parsing | ✅ | ❌ |
| Modify values | ❌ | ✅ |
| Outlives source | ❌ | ✅ |
| Endianness conversion | On write | On read or write |
| Memory usage | Minimal | Proportional to data |
Serde Integration
Serialize and deserialize Rust types directly to/from NBT binary format using serde.
Basic Usage
use ;
use ;
// Serialize to NBT
let player = Player ;
let bytes = to_vec_be.unwrap;
// Deserialize from NBT
let loaded: Player = from_slice_be.unwrap;
assert_eq!;
Convenience Functions
| Function | Description |
|---|---|
to_vec_be / to_vec_le |
Serialize to Vec<u8> |
to_writer_be / to_writer_le |
Serialize to any io::Write |
from_slice_be / from_slice_le |
Deserialize from &[u8] |
from_reader_be / from_reader_le |
Deserialize from any io::Read |
The _be suffix means big-endian (Java Edition), _le means little-endian (Bedrock Edition).
File I/O
use File;
use ;
// Write to file
let mut file = create?;
to_writer_be?;
// Read from file
let file = open?;
let player: Player = from_reader_be?;
Type Mapping
| Rust Type | NBT Tag |
|---|---|
bool, i8, u8 |
Byte |
i16, u16 |
Short |
i32, u32, char |
Int |
i64, u64 |
Long |
f32 |
Float |
f64 |
Double |
String, &str |
String |
Vec<T> |
List |
| struct | Compound |
HashMap<String, T> |
Compound |
Option<T> |
Compound |
| enum variants | Various (see docs) |
Native Array Types
NBT has efficient native array types (ByteArray, IntArray, LongArray).
Deserialization is automatic! Native arrays are detected and read correctly to Vec<T>:
For serialization, use #[serde(with = "...")] for zero-copy performance:
For full serde documentation, see the de and ser module docs.
Contributing
This crate is under active development. Contributions are welcome!
- Bug reports: Please open an issue with a minimal reproducible example
- Feature requests: Open an issue describing the use case
- Pull requests: Fork the repo, make your changes, and submit a PR
License
MIT OR Apache-2.0