byteable/
lib.rs

1#![cfg_attr(not(feature = "std"), no_std)]
2
3//! # Byteable
4//!
5//! A Rust crate for convenient serialization and deserialization of byte-oriented data.
6//!
7//! `byteable` provides traits and utilities for seamless conversion between data structures
8//! and byte arrays, handling both synchronous and asynchronous I/O operations, and managing
9//! endianness.
10//!
11//! ## Features
12//!
13//! - **Byte Conversion Traits**: A modular trait system for byte array conversion:
14//!   - `AssociatedByteArray`: Associates a type with its byte array representation
15//!   - `IntoByteArray`: Converts values into byte arrays
16//!   - `FromByteArray`: Constructs values from byte arrays
17//!   - `TryIntoByteArray` & `TryFromByteArray`: Fallible conversion variants
18//! - **`ReadByteable` & `WriteByteable` Traits**: Extension traits for `std::io::Read` and
19//!   `std::io::Write`, enabling convenient reading and writing of byteable types.
20//! - **`AsyncReadByteable` & `AsyncWriteByteable` Traits** (with `tokio` feature): Asynchronous
21//!   counterparts to `ReadByteable` and `WriteByteable`, designed for use with `tokio`'s async I/O.
22//! - **`EndianConvert` Trait & Wrappers**: Provides methods for converting primitive types between
23//!   different endianness (little-endian and big-endian), along with `BigEndian<T>` and
24//!   `LittleEndian<T>` wrapper types.
25//! - **`#[derive(Byteable)]`** (with `derive` feature): A procedural macro that automatically
26//!   implements the byte conversion traits for structs, significantly simplifying boilerplate. For
27//!   advanced use cases, `#[derive(UnsafeByteableTransmute)]` is also available for manual
28//!   transmute-based implementations.
29//!
30//! ## Quick Start
31//!
32//! Add `byteable` to your `Cargo.toml`:
33//!
34//! ```toml
35//! [dependencies]
36//! byteable = { version = "*", features = ["derive"] }
37//! ```
38//!
39//! ## Basic Usage
40//!
41//! The core workflow involves:
42//! 1. Defining a struct with explicit memory layout (`#[repr(C, packed)]`)
43//! 2. Deriving or implementing the `Byteable` trait
44//! 3. Using extension traits for reading/writing
45//!
46//! ### Example: File I/O
47//!
48//! ```no_run,ignore
49//! # #![cfg(feature = "std")]
50//! use byteable::{Byteable, ReadByteable, WriteByteable};
51//! use std::fs::File;
52//!
53//! #[derive(Byteable, Debug, PartialEq, Clone, Copy)]
54//! struct Packet {
55//!     id: u8,
56//!     #[byteable(little_endian)]
57//!     length: u16,
58//!     data: [u8; 4],
59//! }
60//!
61//! # fn main() -> std::io::Result<()> {
62//! // Create a packet
63//! let packet = Packet {
64//!     id: 42,
65//!     length: 1024,
66//!     data: [0xDE, 0xAD, 0xBE, 0xEF],
67//! };
68//!
69//! // Write to file
70//! let mut file = File::create("packet.bin")?;
71//! file.write_byteable(packet)?;
72//!
73//! // Read from file
74//! let mut file = File::open("packet.bin")?;
75//! let restored: Packet = file.read_byteable()?;
76//!
77//! assert_eq!(packet, restored);
78//! # Ok(())
79//! # }
80//! ```
81//!
82//! ### Example: Network Protocol
83//!
84//! ```no_run
85//! use byteable::Byteable;
86//!
87//! #[derive(Byteable, Debug, Clone, Copy)]
88//! struct NetworkHeader {
89//!     #[byteable(big_endian)]
90//!     magic: u32,       // Network byte order (big-endian)
91//!     version: u8,
92//!     flags: u8,
93//!     #[byteable(little_endian)]
94//!     payload_len: u16, // Little-endian for payload
95//! }
96//! # fn main() {}
97//! ```
98//!
99//! ### Example: Working with TCP Streams
100//!
101//! ```no_run,ignore
102//! # #![cfg(feature = "std")]
103//! use byteable::{Byteable, ReadByteable, WriteByteable};
104//! use std::net::TcpStream;
105//!
106//! #[derive(Byteable, Debug, Clone, Copy)]
107//! struct Message {
108//!     msg_type: u8,
109//!     data: [u8; 16],
110//! }
111//!
112//! # fn main() -> std::io::Result<()> {
113//! let mut stream = TcpStream::connect("127.0.0.1:8080")?;
114//!
115//! // Write message
116//! let msg = Message {
117//!     msg_type: 1,
118//!     data: [0; 16],
119//! };
120//! stream.write_byteable(msg)?;
121//!
122//! // Read response
123//! let response: Message = stream.read_byteable()?;
124//! # Ok(())
125//! # }
126//! ```
127//!
128//! ## Async I/O (with `tokio` feature)
129//!
130//! Enable async support in your `Cargo.toml`:
131//!
132//! ```toml
133//! [dependencies]
134//! byteable = { version = "*", features = ["derive", "tokio"] }
135//! tokio = { version = "1", features = ["full"] }
136//! ```
137//!
138//! Example usage:
139//!
140//! ```no_run
141//! # #[cfg(feature = "tokio")]
142//! {
143//! use byteable::{AsyncReadByteable, AsyncWriteByteable, Byteable};
144//! use tokio::net::TcpStream;
145//!
146//! #[derive(Byteable, Debug, Clone, Copy)]
147//! struct AsyncPacket {
148//!     #[byteable(little_endian)]
149//!     id: u32,
150//!     data: [u8; 8],
151//! }
152//!
153//! # async fn example() -> std::io::Result<()> {
154//! let mut stream = TcpStream::connect("127.0.0.1:8080").await?;
155//!
156//! let packet = AsyncPacket {
157//!     id: 123,
158//!     data: [1, 2, 3, 4, 5, 6, 7, 8],
159//! };
160//!
161//! stream.write_byteable(packet).await?;
162//! let response: AsyncPacket = stream.read_byteable().await?;
163//! # Ok(())
164//! # }
165//! # fn main() {}
166//! # }
167//! ```
168//!
169//! ## Endianness Handling
170//!
171//! The crate provides `BigEndian<T>` and `LittleEndian<T>` wrappers for handling byte order:
172//!
173//! ```
174//! use byteable::{BigEndian, LittleEndian, IntoByteArray};
175//!
176//! // Create endian-aware values
177//! let big = BigEndian::new(0x12345678u32);
178//! let little = LittleEndian::new(0x12345678u32);
179//!
180//! // Get raw bytes (in specified endianness)
181//! assert_eq!(big.into_byte_array(), [0x12, 0x34, 0x56, 0x78]);
182//! assert_eq!(little.into_byte_array(), [0x78, 0x56, 0x34, 0x12]);
183//!
184//! // Convert back to native value
185//! assert_eq!(big.get(), 0x12345678u32);
186//! assert_eq!(little.get(), 0x12345678u32);
187//! ```
188//!
189//! ## Enum Support
190//!
191//! The `#[derive(Byteable)]` macro supports C-like enums with explicit discriminants:
192//!
193//! ```
194//! # #[cfg(feature = "derive")]
195//! use byteable::{Byteable, IntoByteArray, TryFromByteArray};
196//!
197//! # #[cfg(feature = "derive")]
198//! #[derive(Byteable, Debug, Clone, Copy, PartialEq)]
199//! #[repr(u8)]
200//! enum Status {
201//!     Idle = 0,
202//!     Running = 1,
203//!     Completed = 2,
204//!     Failed = 3,
205//! }
206//!
207//! # #[cfg(feature = "derive")]
208//! # fn main() -> Result<(), byteable::EnumFromBytesError> {
209//! let status = Status::Running;
210//! let bytes = status.into_byte_array();
211//! assert_eq!(bytes, [1]);
212//!
213//! // Use TryFromByteArray for fallible conversion
214//! let restored = Status::try_from_byte_array(bytes)?;
215//! assert_eq!(restored, Status::Running);
216//!
217//! // Invalid discriminants return an error
218//! let invalid = Status::try_from_byte_array([255]);
219//! assert!(invalid.is_err());
220//! # Ok(())
221//! # }
222//! # #[cfg(not(feature = "derive"))]
223//! # fn main() {}
224//! ```
225//!
226//! ### Enum with Endianness
227//!
228//! Enums support type-level endianness attributes for multi-byte discriminants:
229//!
230//! ```
231//! # #[cfg(feature = "derive")]
232//! use byteable::Byteable;
233//!
234//! # #[cfg(feature = "derive")]
235//! // Little-endian (common for file formats)
236//! #[derive(Byteable, Debug, Clone, Copy, PartialEq)]
237//! #[repr(u16)]
238//! #[byteable(little_endian)]
239//! enum Command {
240//!     Start = 0x1000,
241//!     Stop = 0x2000,
242//!     Pause = 0x3000,
243//! }
244//!
245//! # #[cfg(feature = "derive")]
246//! // Big-endian (common for network protocols)
247//! #[derive(Byteable, Debug, Clone, Copy, PartialEq)]
248//! #[repr(u32)]
249//! #[byteable(big_endian)]
250//! enum HttpStatus {
251//!     Ok = 200,
252//!     NotFound = 404,
253//!     InternalError = 500,
254//! }
255//! # fn main() {}
256//! ```
257//!
258//! ### Enum Requirements
259//!
260//! When deriving `Byteable` for enums:
261//!
262//! 1. **Explicit repr type required**: `#[repr(u8)]`, `#[repr(u16)]`, `#[repr(u32)]`, `#[repr(u64)]`,
263//!    `#[repr(i8)]`, `#[repr(i16)]`, `#[repr(i32)]`, or `#[repr(i64)]`
264//! 2. **Unit variants only**: All variants must be unit variants (no fields)
265//! 3. **Explicit discriminants**: All variants must have explicit values
266//! 4. **Fallible conversion**: Use `TryFromByteArray` (not `FromByteArray`) because invalid
267//!    discriminants return `EnumFromBytesError`
268//!
269//! ### Nested Enums in Structs
270//!
271//! Use the `#[byteable(try_transparent)]` attribute for enum fields in structs:
272//!
273//! ```
274//! # #[cfg(feature = "derive")]
275//! use byteable::Byteable;
276//!
277//! # #[cfg(feature = "derive")]
278//! #[derive(Byteable, Debug, Clone, Copy, PartialEq)]
279//! #[repr(u8)]
280//! enum MessageType {
281//!     Data = 1,
282//!     Control = 2,
283//!     ErrorMsg = 3,
284//! }
285//!
286//! # #[cfg(feature = "derive")]
287//! #[derive(Byteable, Clone, Copy)]
288//! struct Message {
289//!     #[byteable(try_transparent)]
290//!     msg_type: MessageType,
291//!     #[byteable(big_endian)]
292//!     sequence: u32,
293//!     payload: [u8; 16],
294//! }
295//! # fn main() {}
296//! ```
297//!
298//! ## Safety Considerations
299//!
300//! The `#[derive(Byteable)]` macro uses `core::mem::transmute` internally, which is unsafe.
301//! When using this macro, ensure that:
302//!
303//! 1. All fields are primitive types or have endianness attributes (`#[byteable(big_endian)]`, `#[byteable(little_endian)]`)
304//! 2. The struct doesn't contain types with invalid bit patterns (e.g., `bool`, `char`)
305//! 3. C-like enums with explicit discriminants are safe (supported via derive)
306//! 4. Complex enums with fields are **not** safe
307//!
308//! For types with complex invariants (like `String`, `Vec`, references, etc.), do **not** use
309//! the `Byteable` derive macro. Use only with plain old data (POD) types.
310//!
311//! ## Advanced Usage
312//!
313//! ### Custom `Byteable` Implementation
314//!
315//! The `#[derive(Byteable)]` macro handles most use cases automatically, including
316//! endianness conversion via attributes:
317//!
318//! ```
319//! #![cfg(feature = "derive")]
320//! use byteable::{Byteable, IntoByteArray, FromByteArray};
321//!
322//! #[derive(Byteable, Debug, PartialEq, Clone, Copy)]
323//! struct Point {
324//!     #[byteable(little_endian)]
325//!     x: i32,
326//!     #[byteable(little_endian)]
327//!     y: i32,
328//! }
329//!
330//! # fn main() {
331//! let point = Point { x: 10, y: 20 };
332//! let bytes = point.into_byte_array();
333//! let restored = Point::from_byte_array(bytes);
334//! assert_eq!(point, restored);
335//! # }
336//! ```
337//!
338//! For advanced cases, you can still use the `impl_byteable_via!` macro with manual
339//! implementations. See the trait documentation for details.
340//!
341//! ## Feature Flags
342//!
343//! - `derive`: Enables the `#[derive(Byteable)]` procedural macro (default: enabled)
344//! - `tokio`: Enables async I/O traits for use with tokio (default: disabled)
345//!
346//! ## Performance
347//!
348//! This crate is designed for zero-copy, zero-overhead serialization. Operations like
349//! `into_byte_array` and `from_byte_array` typically compile down to simple memory operations
350//! or even no-ops when possible.
351
352mod byte_array;
353mod byteable_trait;
354mod derive_safety_helpers;
355mod endian;
356
357#[cfg(feature = "std")]
358mod io;
359
360extern crate self as byteable; // used to resolve derive macros in examples etc.
361
362#[cfg(feature = "tokio")]
363mod async_io;
364
365#[cfg(feature = "derive")]
366pub use byteable_derive::{Byteable, UnsafeByteableTransmute};
367
368pub use byte_array::ByteArray;
369
370pub use byteable_trait::{
371    AssociatedByteArray, BoolRaw, CharRaw, Discriminant, EnumFromBytesError, FromByteArray,
372    HasRawType, IntoByteArray, TryFromByteArray, TryHasRawType, TryIntoByteArray,
373};
374
375#[cfg(feature = "std")]
376pub use io::{ReadByteable, ReadTryByteable, TryByteableError, WriteByteable, WriteTryByteable};
377
378#[cfg(feature = "tokio")]
379pub use async_io::{
380    AsyncReadByteable, AsyncReadTryByteable, AsyncWriteByteable, AsyncWriteTryByteable,
381};
382
383pub use endian::{BigEndian, EndianConvert, LittleEndian};
384
385pub use derive_safety_helpers::ValidBytecastMarker;