Crate bitsong

Crate bitsong 

Source
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 write my_struct into a buffer buf
  • Use MyStruct::from_song(&buf) to read type MyStruct from a buffer buf

§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_panic

Modules§

magic
maybe_unwritten_max_bytes
option
result

Structs§

ConstSongSizeImplFromConstSongSize
ConstSongSizeValue
MultiplyConstSongSizeImpl

Enums§

FromSongError
ToSongError

Traits§

ConstSongSize
Automatically implemented when SongSize::song_size is the same for all instances of the impl of the type.
FromSong
HasSongSize
SongDiscriminant
Used for generated enum discriminant types.
SongSize
Something that has a serializable size. Required for ToSong and FromSong.
ToSong

Derive Macros§

FromSong
SongSize
SpiError
ToSong