Expand description
§Bitsong
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.
§Getting started
- Use
#[derive(SongSize, FromSong, ToSong)]on your struct/enum (or implement the traits manually) - Check how many bytes will be written with
my_struct.song_size() - Use
my_struct.to_song(&mut buf)to writemy_structinto a bufferbuf - Use
MyStruct::from_song(&buf)to read typeMyStructfrom a bufferbuf
§How it works
The 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.
§Example code
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())
}§Running no_panic tests
cargo test --profile release --features no_panicModules§
Structs§
Enums§
Traits§
- Const
Song Size - Automatically implemented when SongSize::song_size is the same for all instances of the impl of the type.
- From
Song - HasSong
Size - Song
Discriminant - Used for generated enum discriminant types.
- Song
Size - Something that has a serializable size. Required for ToSong and FromSong.
- ToSong