I am curious to see how much boilerplate one can save through built-in reflection.
A little background
My idea behind structured logging is to use various small tailored types to separate content from representation. Instead of unstructured logger.info("Found a bar with {} foos", bar.foo) one uses something like logger.info(FoundBar{ _bar: bar })
My Rust-ish approach
- define a
Logtrait - provide a default implementation that calls the Serde machinery to serialize the type (to JSON in this example)
- define loggable types easily by letting them "inherit" the default implementation
- profit
Define the trait, providing a default impl:
trait Log { fn to_log(&self) -> String { serde_json::to_string(&self).unwrap() } } (RLS is already drawing angry red squiggles, but bear with me)
Define a simple type to be logged:
#[derive(Serialize)] struct Message { msg: String, } and let it use the default implementation:
impl Log for Message {} and finally the polymorphic logging function defined in terms of the trait:
fn log(log: &Log) { println!("serialized = {}", log.to_log()); } The compiler complains:
error[E0277]: the trait bound `Self: _IMPL_DESERIALIZE_FOR_Message::_serde::Serialize` is not satisfied --> src\main.rs:8:9 | 8 | serde_json::to_string(&self).unwrap() | ^^^^^^^^^^^^^^^^^^^^^ the trait `_IMPL_DESERIALIZE_FOR_Message::_serde::Serialize` is not implemented for `Self` | = help: consider adding a `where Self: _IMPL_DESERIALIZE_FOR_Message::_serde::Serialize` bound = note: required because of the requirements on the impl of `_IMPL_DESERIALIZE_FOR_Message::_serde::Serialize` for `&Self` = note: required by `serde_json::ser::to_string` Adding the where Self suggestion to my trait function only produces different errors (error[E0433]: failed to resolve. Use of undeclared type or module _IMPL_DESERIALIZE_FOR_Message), but apart from that it seems like a Bad Idea(TM) to have this implementation detail of Serde leak into my code.
How do I portably constrain my trait (using where?) to only apply to types that have the correct derive? Even better, can I "inject" the derive functionality into types using the trait?