Implementing Format
#[derive(Format)]
The easiest way to implement the Format
trait for a struct
or enum
is to use the derive
attribute.
#![allow(unused)] fn main() { extern crate defmt; use defmt::Format; #[derive(Format)] struct Header { source: u8, destination: u8, sequence: u16, } #[derive(Format)] struct Descriptor; #[derive(Format)] enum Request { GetDescriptor { descriptor: Descriptor, length: u16 }, SetAddress { address: u8 }, } }
Like built-in derives (e.g. #[derive(Debug)]
), #[derive(Format)]
will add Format
bounds to the generic type parameters of the struct.
⚠️ Do not use the API used by the expansion of the
derive(Format)
macro; it is unstable.
By default the derive assumes defmt
to exist in your crate's dependencies (extern crate prelude).
If that is not the case you can overwrite the crate path the derive should use in its expansion via the defmt(crate = path)
helper attribute.
The alternative defmt
crate path needs to be specified for every derive(other_defmt::Format)
macro.
#![allow(unused)] fn main() { extern crate defmt as other_defmt; #[derive(other_defmt::Format)] #[defmt(crate = other_defmt)] struct Header; }
Feature-gated #[derive(Format)]
It is also possible to feature-gate the implementation by defining
derive
implementation via cfg_attr(feature = ...)
:
#![allow(unused)] fn main() { #[derive(Copy, Clone)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] struct Header { // ... } }
Manual implementation with write!
It is also possible to implement the Format
trait manually.
This trait has a single required method: format
.
In this method you need to format the value (self
) into the given Formatter
argument using the defmt::write!
macro.
Example below:
#![allow(unused)] fn main() { extern crate defmt; // value read from a MMIO register named "CRCCNF" struct CRCCNF { bits: u32, } impl defmt::Format for CRCCNF { fn format(&self, f: defmt::Formatter) { // format the bitfields of the register as struct fields defmt::write!( f, "CRCCNF {{ LEN: {0=0..2}, SKIPADDR: {0=8..10} }}", self.bits, ) } } }
Newtypes
If you need to implement Format
for some "newtype" struct you can delegate the formatting to the inner type.
Example below:
#![allow(unused)] fn main() { extern crate defmt; struct MyU8 { inner: u8 } impl defmt::Format for MyU8 { fn format(&self, f: defmt::Formatter) { self.inner.format(f) } } }
Uncompressed adapters
If you quickly want to get some code running and do not care about it being efficient you can use the two adapter types Display2Format
and Debug2Format
.
⚠️ These adapters disable compression and use the
core::fmt
code on-device! You should always preferdefmt::Format
overDebug
whenever possible!
Note that this always uses {:?}
to format the contained value, meaning that any provided defmt display hints will be ignored.
When using #[derive(Format)]
you may use the #[defmt()]
attribute on specific fields to use these adapter types.
Example below:
#![allow(unused)] fn main() { extern crate defmt; use defmt::Format; mod serde_json { #[derive(Debug)] pub enum Error {} } #[derive(Format)] enum Error { Serde(#[defmt(Debug2Format)] serde_json::Error), ResponseTooLarge, } struct Celsius(); impl std::fmt::Display for Celsius { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { Ok(()) } } #[derive(Format)] struct Reading { #[defmt(Display2Format)] temperature: Celsius, } }