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.

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 prefer defmt::Format over Debug 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,
}
}