| Crates.io | bitsong |
| lib.rs | bitsong |
| version | 0.1.3 |
| created_at | 2025-12-17 23:28:13.592909+00 |
| updated_at | 2025-12-17 23:44:30.19918+00 |
| description | Fast `#[no_std]` macro-based serialization/deserialization. |
| homepage | |
| repository | https://codeberg.org/Serena/bitsong/ |
| max_upload_size | |
| id | 1991308 |
| size | 21,254 |
Fast #[no_std] macro-based serialization/deserialization into a minimal binary format. Makes no memory allocations, doesn't use tagging (except for enum discriminants), and supports enums and enums with tuple variants. Doesn't support strings (but you can use a buffer) or struct enum variants.
#[derive(SongSize, FromSong, ToSong)] on your struct/enum (or implement the traits manually)my_struct.song_size()my_struct.to_song(&mut buf) to write my_struct into a buffer bufMyStruct::from_song(&buf) to read type MyStruct from a buffer bufThe derive macros generate the serializers and deserializers. The save format is dead simple; for structs, it is every field in the order they appear (without any tagging bytes), and for enums there is one discriminant field and then any associated enum data. It is very similar to what you would get with mem::transmute, except without any padding bytes and with the additional guarantee that it'll always work cross-platform.
This code has been used by Case Rocket Team and seems to work well enough, though some of the macro implementation is a bit messy. We use it for reading and writing low bandwidth LoRa radio packets.
use crate::*;
#[derive(Clone, Copy, Debug, PartialEq, Eq, FromSong, ToSong, SongSize)]
#[repr(u8)]
enum Color {
Unknown = 0x00,
Red = 0x01,
Green = 0x02,
Blue = 0x03
}
#[derive(Clone, Debug, PartialEq, Eq, FromSong, ToSong, SongSize)]
struct Cat {
fluffy_paws: bool
}
#[derive(Clone, Debug, PartialEq, Eq, FromSong, ToSong, SongSize)]
#[song(discriminant(PetType = u8))]
enum Pet {
Dog,
Cat(Cat),
Fish(Color)
}
#[derive(Clone, Debug, PartialEq, Eq, FromSong, ToSong, SongSize)]
struct Person {
name: [u8; 16],
age: u8,
favorite_color: Color,
pet: Option<Pet>
}
fn new_joe() -> Person {
let mut name = [0; 16];
name[0..3].copy_from_slice(b"Joe");
Person {
name,
age: 21,
favorite_color: Color::Green,
pet: Some(Pet::Cat(Cat {
fluffy_paws: true
}))
}
}
const JOE_SONG: [u8; 21] = [74, 111, 101, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 21, 2, 1, 1, 1];
#[test]
fn serialize() {
let mut buf = [0; 21];
new_joe().to_song(&mut buf).unwrap();
assert_eq!(buf, JOE_SONG);
}
#[test]
fn deserialize() {
let joe = Person::from_song(&JOE_SONG).unwrap();
assert_eq!(joe, new_joe())
}
cargo test --profile release --features no_panic