knurling-rs / defmt

Efficient, deferred formatting for logging on embedded systems
https://defmt.ferrous-systems.com/
Apache License 2.0
848 stars 77 forks source link

`derive(Format)`: add field attribute to map a field with a method prior to formatting it #502

Open japaric opened 3 years ago

japaric commented 3 years ago

Use case

You want to derive Format an a struct that contains fields whose types don't implement the Format trait but can be easily mapped to a type that does. Examples include structs with heapless::Vec (implements Format as of 0.7.0) and NonZero* (see #) fields

Strawman proposal

// == `write!(f, "S1 {{ list: {}, .. }}", self.list.as_slice(), /* .. */)`
#[derive(Format)]
struct S1 {
    #[format(method = "as_slice")]
    list: Vec<u8, N>,
    // .. other fields ..
}

// == `write!(f, "S2 {{ index: {}, .. }}", self.index.get(), /* .. */)`
#[derive(Format)]
struct S2 {
    #[format(method = "get")]
    index: NonZeroU8,
    // .. other fields ..
}

Alternatives

Unresolves questions

jamesmunns commented 3 years ago

Just adding that I ran into this today as well, using a bunch of AtomicU32s for profiling counters. I ended up making a mirror-structure out of regular u32s just to derive Format on it.

struct Profiler {
    spim_p0_ints: AtomicU32,
    spim_p1_ints: AtomicU32,
    spim_p2_ints: AtomicU32,
    spim_p3_ints: AtomicU32,
    usb_writes: AtomicU32,
    report_sers: AtomicU32,
    bbq_push_bytes: AtomicU32,
    bbq_pull_bytes: AtomicU32,
    idle_loop_iters: AtomicU32,
}

#[derive(Format)]
struct Report {
    spim_p0_ints: u32,
    spim_p1_ints: u32,
    spim_p2_ints: u32,
    spim_p3_ints: u32,
    usb_writes: u32,
    report_sers: u32,
    bbq_push_bytes: u32,
    bbq_pull_bytes: u32,
    idle_loop_iters: u32,
}

Having something like this would have been great!

Urhengulas commented 3 years ago

What do you think about doing it similar to serde, which gives you the possibility to serialize a field using a function (https://serde.rs/field-attrs.html#serialize_with)?

The advantage I see is that the user can provide their own function for formatting and is not limited to exiting methods. If the user wants to use an existing method, they can still call it in the function.

japaric commented 3 years ago

(https://serde.rs/field-attrs.html#serialize_with)?

Something like this may be difficult to implement with defmt 0.2 because the closest thing we have to a serde "Serializer" is Formatter and that is consumed when you call write! on it. This sounds doable with the multi-write! proposed in #492 however

Urhengulas commented 3 years ago

(https://serde.rs/field-attrs.html#serialize_with)?

Something like this may be difficult to implement with defmt 0.2 because the closest thing we have to a serde "Serializer" is Formatter and that is consumed when you call write! on it. This sounds doable with the multi-write! proposed in #492 however

I mainly wanted to point out that they let you specify functions and not methods.

We could probably omit the formatter from the signature and just have a bound of fn(&T) -> impl Format. Then I can either pass Vec::as_slice (from your example above) or my_custom_fn.