dlw
dlw

Reputation: 323

How to specify that all implementers of a trait must also implement Serialize?

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 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?

Upvotes: 4

Views: 1751

Answers (2)

dlw
dlw

Reputation: 323

Using trait inheritance works, but using the right Serde trait, not the compiler-suggested one:

trait Log: serde::Serialize {
    fn to_log(&self) -> String {
        serde_json::to_string(&self).unwrap()
    }
}

Upvotes: 1

Shepmaster
Shepmaster

Reputation: 430981

If you create a MCVE of your problem on the playground, you get a more accurate error:

error[E0277]: the trait bound `Self: serde::Serialize` is not satisfied
 --> src/lib.rs:6:9
  |
6 |         serde_json::to_string(&self).unwrap()
  |         ^^^^^^^^^^^^^^^^^^^^^ the trait `serde::Serialize` is not implemented for `Self`
  |
  = help: consider adding a `where Self: serde::Serialize` bound
  = note: required because of the requirements on the impl of `serde::Serialize` for `&Self`
  = note: required by `serde_json::ser::to_string`

Following the suggestion, but using the idiomatic supertrait syntax, answers your question:

trait Log: serde::Serialize {
    fn to_log(&self) -> String {
        serde_json::to_string(&self).unwrap()
    }
}

You'll need to change your log function for object-safety reasons:

fn log(log: &impl Log) {
    println!("serialized = {}", log.to_log());
}

See also:

Upvotes: 2

Related Questions