femtopb/
lib.rs

1#![cfg_attr(not(test), no_std)]
2//! # `femtopb`
3//!
4//! A tiny footprint, `#[no_std]`, no-`alloc`, no-panic Protobuf serialization library.  This allows
5//! you to communicate using Protobuf on constrained platforms, like bare-metal MCUs with very
6//! limited RAM.
7//!
8//! Yes, you heard it right: this library lets you serialize and deserialize Protobuf messages
9//! without any dynamic memory/heap allocation.
10//!
11//! The library takes care of using simple types with limited use of generics when possible, to
12//! avoid monomorphization code size explosion.  The runtime also consists of many tiny functions
13//! so that the ones that aren't used can get optimized away.
14//!
15//! During testing of this crate, checks are made to ensure that `femtopb` code cannot panic.  If
16//! you want to leverage the no-panic checks yourself to debug your own project, enable the
17//! `assert-no-panic` crate feature.  It is not necessarily a good idea to enable this feature for
18//! your release code, as enabling this feature might change the generated code slightly.
19//!
20//! ## Defining message types
21//!
22//! `femtopb` enables encoding and decoding of messages by deriving the `femtopb::Message` trait.
23//! Writing the code for these message types is usually done using the `femtopb-build` crate, but
24//! we will write some code manually here for illustration purposes.
25//!
26//! A simple message type might look like this:
27//!
28//! ```
29//! #[derive(Clone, femtopb::Message)]
30//! pub struct Person<'a> {
31//!     #[femtopb(uint32, tag = 1)]
32//!     pub age: u32,
33//!     #[femtopb(string, tag = 2)]
34//!     pub name: &'a str,
35//!     #[femtopb(unknown_fields)]
36//!     pub unknown_fields: femtopb::UnknownFields<'a>,
37//! }
38//! ```
39//!
40//! The struct can be used just like any other Rust struct, using the simple built-in types of the
41//! language for the most part.
42//!
43//! The lifetime parameter on the struct is mandatory, and is used to refer to the lifetime of any
44//! dynamically sized data inside the message.  When a message is decoded from raw bytes, types like
45//! `&'a str` will borrow its contents from the original raw byte buffer.
46//!
47//! The `#[femtopb(...)]` attribute provides additional metadata for how the type gets serialized.
48//! All the semantics are described in the [official protobuf docs](https://protobuf.dev/programming-guides/encoding/),
49//! but as a brief overview, the `tag` number corresponds to the binary ID of the field as it is
50//! serialized on the wire (and should hence be unique, and not change, for your given message type).
51//! Other bits in the attribute influence how the data gets serialized more specifically; again,
52//! please consult the official protobuf documentation for details.
53//!
54//! The `#[femtopb(unknown_fields)]` field is used to preserve fields that are not yet known to our
55//! current code.  For example, if the writer of a message has added a new field to the message
56//! (which is a backwards-compatible change), the data of this field will be preserved inside the
57//! `unknown_fields` field.  The API for this is still a work in progress.
58//!
59//! ## Encoding and decoding
60//!
61//! Given a message definition like the one above, you can easily encode and decode data to/from
62//! bytes, using the associated trait methods:
63//!
64//! ```
65//! use femtopb::Message as _;
66//!
67//! // Adding some more derived traits to aid with the example below:
68//! #[derive(Clone, Debug, PartialEq, femtopb::Message)]
69//! pub struct Person<'a> {
70//!     #[femtopb(uint32, tag = 1)]
71//!     pub age: u32,
72//!     #[femtopb(string, tag = 2)]
73//!     pub name: &'a str,
74//!     #[femtopb(unknown_fields)]
75//!     pub unknown_fields: femtopb::UnknownFields<'a>,
76//! }
77//!
78//! fn main() {
79//!     let person = Person { age: 32, name: "David", ..Default::default() };
80//!     // Create a new buffer filled with zeroes.  This of course doesn't need to be dynamically
81//!     // allocated; at this point, you could use a stack-allocated buffer, or `'static` memory
82//!     // region, for example.  Here, we use a `Vec` for simplicity.
83//!     // The buffer MUST already have the right length, since we can't grow the buffer without
84//!     // dynamic memory allocation.
85//!     let mut buf = vec![0; person.encoded_len()];
86//!
87//!     // Encode the Person to the buffer
88//!     person.encode(&mut buf.as_mut_slice()).unwrap();
89//!     // Decode a new Person from the same buffer
90//!     let new_person = Person::decode(buf.as_slice()).unwrap();
91//!
92//!     // The same information should be preserved!
93//!     assert_eq!(person, new_person);
94//! }
95//! ```
96//!
97//! ## Repeated and packed fields
98//!
99//! Quite commonly, you'll need to encode a collection of things.  In `femtopb`, since we can't
100//! do any dynamic memory allocation, we need to use special collection types that borrow all of
101//! their memory from the original buffer, instead of types like `Vec` that use dynamic allocation.
102//!
103//! These types decode their values on-the-fly, when you iterate through them, instead of eagerly
104//! at `decode()` time.  Hence, they also need to know about an `ItemEncoding`, which is used to
105//! decode values lazily after the main `Message::decode()` call has returned.  This means that the
106//! type is quite long, but you usually won't see it in code generated by `femtopb-build`.
107//!
108//! There are repeated fields, which are usually used for composite types like messages, and packed
109//! fields, which use a more efficient encoding for scalar types.  You usually don't have to make
110//! an active choice as to which type to use; `femtopb-build` will generate the right type for you.
111//!
112//! ```
113//! use femtopb::Message as _;
114//! use femtopb::item_encoding;
115//! use femtopb::repeated;
116//!
117//! #[derive(Clone, Debug, PartialEq, femtopb::Message)]
118//! pub struct WeatherStationEvent<'a> {
119//!     #[femtopb(bytes, tag = 1)]
120//!     serial_id: &'a [u8],
121//!     #[femtopb(message, repeated, tag = 2)]
122//!     new_readings: repeated::Repeated<'a, TempReading<'a>, item_encoding::Message<'a, TempReading<'a>>>,
123//!     #[femtopb(unknown_fields)]
124//!     pub unknown_fields: femtopb::UnknownFields<'a>,
125//! }
126//!
127//! #[derive(Clone, Debug, PartialEq, femtopb::Message)]
128//! pub struct TempReading<'a> {
129//!     #[femtopb(uint64, tag = 1)]
130//!     id: u64,
131//!     #[femtopb(float, tag = 2)]
132//!     degrees_c: f32,
133//!     #[femtopb(float, tag = 3)]
134//!     pressure_hpa: f32,
135//!     #[femtopb(unknown_fields)]
136//!     pub unknown_fields: femtopb::UnknownFields<'a>,
137//! }
138//!
139//! fn main() {
140//!     let new_readings = &[
141//!         TempReading { id: 1234, degrees_c: 23.0, ..Default::default() },
142//!         TempReading { id: 1235, degrees_c: 23.2, ..Default::default() },
143//!         TempReading { id: 1236, degrees_c: 23.4, ..Default::default() },
144//!     ];
145//!     let event = WeatherStationEvent {
146//!         serial_id: b"mystation-abc123",
147//!         new_readings: repeated::Repeated::from_slice(new_readings),
148//!         ..Default::default()
149//!     };
150//!
151//!     let mut buf = vec![0; event.encoded_len()];
152//!     event.encode(&mut buf.as_mut_slice()).unwrap();
153//!     // ... send buf over a LoRa network or something ...
154//!     let new_event = WeatherStationEvent::decode(buf.as_slice()).unwrap();
155//!     assert_eq!(new_event.serial_id, b"mystation-abc123");
156//!     for reading in &new_event.new_readings {
157//!         let TempReading { id, degrees_c, .. } = reading.unwrap();
158//!         println!("{id}: {degrees_c}");
159//!         // Prints:
160//!         // 1234: 23.0
161//!         // 1235: 23.2
162//!         // 1236: 23.4
163//!     }
164//! }
165//! ```
166//!
167//! ## Enums
168//!
169//! Enums are represented via the `femtopb::Enumeration` trait.  The only special requirements on
170//! enums are that the enum must derive `Clone` and `Copy`, and all variants must have a
171//! discriminant (number value) assigned to them.  Also, generated enums will always have the first
172//! variant be its default value.
173//!
174//! Enums cannot themselves be encoded; they must exist as a field on a `Message`, and then the
175//! wrapping message can be encoded.
176//!
177//! ```
178//! #[derive(Clone, Copy, Default, femtopb::Enumeration)]
179//! pub enum BasicEnumeration {
180//!     #[default]
181//!     ZERO = 0,
182//!     ONE = 1,
183//!     TWO = 2,
184//!     THREE = 3,
185//! }
186//! ```
187//!
188//! ## Oneofs
189//!
190//! Oneofs are enums where each variant wraps exactly one other value.  The `oneof` terminology
191//! comes from the related [protobuf concept](https://protobuf.dev/programming-guides/proto3/#oneof).
192//!
193//! Oneofs can be used to encode mutually exclusive values.  On the wire, oneofs are simply encoded
194//! as the field out of a group of mutually exclusive fields that was actually populated.
195//!
196//! Like with enums, oneofs can't be encoded on their own, and must exist as a field on a message.
197//!
198//! ```
199//! #[derive(femtopb::Oneof)]
200//! pub enum StorePageReview<'a> {
201//!     #[femtopb(int32, tag = 8)]
202//!     Rating(i32),
203//!     #[femtopb(string, tag = 9)]
204//!     WrittenReview(&'a str),
205//! }
206//! ```
207//!
208//! ## Other features
209//!
210//! There are simpler concepts like optional fields, recursive messages, etc. that are not yet
211//! covered in this documentation.  Feel free to request documentation for anything that you feel
212//! is missing!
213//!
214//! ## Unsupported protobuf features
215//!
216//! This library does not (yet) support groups (which were deprecated since Protobuf 1) and maps.
217//!
218//! ## Acknowledgements
219//!
220//! This library is heavily inspired by the amazing `prost` library, and some tests and core
221//! algorithms were copied from that library.  However, the architecture of `femtopb` compared to
222//! `prost` ended up being significantly different, so mostly only the build infrastructure of
223//! `prost-build` was re-used in the creation of `femtopb-build`.
224
225mod bits;
226mod list;
227
228pub mod deferred;
229pub mod encoding;
230pub mod enumeration;
231pub mod error;
232pub mod item_encoding;
233pub mod message;
234pub mod oneof;
235pub mod packed;
236pub mod repeated;
237pub mod unknown_fields;
238
239#[doc(hidden)]
240pub mod runtime;
241
242pub use enumeration::EnumValue;
243pub use enumeration::Enumeration;
244pub use femtopb_derive::Enumeration;
245pub use femtopb_derive::Message;
246pub use femtopb_derive::Oneof;
247pub use message::Message;
248pub use oneof::Oneof;
249pub use packed::Packed;
250pub use repeated::Repeated;
251pub use unknown_fields::UnknownFields;