Rust/Serde: Serialize events with polymorphic payload - rust

I have events with a polymorphic payload property:
use serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize, Serialize)]
pub struct Event<'a> {
pub topic: String,
pub key: Option<String>,
pub payload: Box<dyn EventPayload<'a>>,
}
The only requirement for my payload, is that I want to serialize it using serde_json:
pub trait EventPayload<'a> : Debug + Deserialize<'a> + Serialize {}
The compiler does not allow this pattern:
error[E0038]: the trait `EventPayload` cannot be made into an object
--> pong/src/event.rs:12:26
|
12 | pub payload: Box<dyn EventPayload<'a>>,
| ^^^^^^^^^^^^^^^^^^^^ `EventPayload` cannot be made into an object
|
note: for a trait to be "object safe" it needs to allow building a vtable to allow the call to be resolvable dynamically; for more information visit <https://doc.rust-lang.org/reference/items/traits.html#object-safety>
I know I could use enums for this use case:
enum Event {
EventA(EventAPayload),
EventB(EventBPayload),
}
However I don't like this solution because I would have to pin down types of objects that are defined outside of the application (e.g. by a schema registry).
What is the correct pattern to use in rust?

However I don't like this solution because I would have to pin down types of objects that are defined outside of the application (e.g. by a schema registry).
If you don't want to pin down the types, then the compiler won't be able to generate deserialization code for that either. The compiler cannot generate deserialization code for types it doesn't know.
I think the correct solution here would be to implement Deserialize yourself instead of deriving it. It's not actually that hard, don't worry. But the compiler won't be able to figure out how to deserialize an external type, you will have to implement that yourself.

Related

How do I avoid Enum + Trait pattern when a struct is not object safe?

I get the implications of object safety, but I'm trying to find an idiomatic way to solve for this situation.
Say I have two structs that share common behavior and also need to derive PartialEq for comparison in another part of the program:
trait Growl:PartialEq {
fn growl(&self);
}
#[derive(PartialEq)]
struct Pikachu;
#[derive(PartialEq)]
struct Porygon;
impl Growl for Pikachu {
fn growl(&self) {
println!("pika");
}
}
impl Growl for Porygon {
fn growl(&self) {
println!("umm.. rawr?");
}
}
In another struct, I want to hold a Vec of these objects. Since I can't use a trait object with Vec<Box<Growl>>...
struct Region{
pokemon: Vec<Box<dyn Growl>>,
}
// ERROR: `Growl` cannot be made into an object
... I need to get more creative. I read this article, which suggests using an enum or changing the trait. I haven't yet explored type erasure, but it seems heavy-handed for my use case. Using an enum like this is what I've ended up doing but it feels unnecessarily complex
enum Pokemon {
Pika(Pikachu),
Pory(Porygon),
}
Someone coming through this code in the future now needs to understand the individual structs, the trait (which provides all functionality for the structs), and the wrapper enum type to make changes.
Is there a better solution for this pattern?
I read this article, which suggests using an enum or changing the trait. I haven't yet explored type erasure, but it seems heavy-handed for my use case.
Type erasure is just a synonym term for dynamic dispatch - even your original Box<dyn Growl> "erases the type" of the Pokemon. What you want here is to continue in the same vein, by creating a new trait better taylored to your use case and providing a blanket implementation of that trait for any type that implements the original trait.
It sounds complex, but it's actually very simple, much simpler than erased-serde, which has to deal with serde's behemoth traits. Let's go through it step by step. First, you create a trait that won't cause issues with dynamic dispatch:
/// Like Growl, but without PartialEq
trait Gnarl {
// here you'd have only the methods which are actually needed by Region::pokemon.
// Let's assume it needs growl().
fn growl(&self);
}
Then, provide a blanket implementation of your new Gnarl trait for all types that implement the original Growl:
impl<T> Gnarl for T
where
T: Growl,
{
fn growl(&self) {
// Here in the implementation `self` is known to implement `Growl`,
// so you can make use of the full `Growl` functionality, *including*
// things not exposed to `Gnarl` like PartialEq
<T as Growl>::growl(self);
}
}
Finally, use the new trait to create type-erased pokemon:
struct Region {
pokemon: Vec<Box<dyn Gnarl>>,
}
fn main() {
let _region = Region {
pokemon: vec![Box::new(Pikachu), Box::new(Porygon)],
};
}
Playground

How do I work around the "upstream crates may add a new impl of trait" error?

I have created a trait for transforming from some values to a type I need. That conversion is already covered by From/Into for many types, but not everything I want. I thought I could exploit this, but quickly got an error "upstream crates may add a new impl of trait".
(stripped-down example in the playground)
pub trait Cookable {
fn cook(self) -> (String, Vec<i8>);
}
impl<T: Into<Vec<i8>>> Cookable for T {
fn cook(self) -> (String, Vec<i8>) {
(String::from("simple"), self.into())
}
}
impl Cookable for &str {
fn cook(self) -> (String, Vec<i8>) {
(String::from("smelly"), vec![self.len()])
}
}
That triggers the following error:
error[E0119]: conflicting implementations of trait `Cookable` for type `&str`:
--> src/lib.rs:11:1
|
5 | impl<T: Into<Vec<i8>>> Cookable for T {
| ------------------------------------- first implementation here
...
11 | impl Cookable for &str {
| ^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `&str`
|
= note: upstream crates may add a new impl of trait `std::convert::From<&str>` for type `std::vec::Vec<i8>` in future versions
I am worried that the only way to work around this error is to specify individual trait implementations for every one of the types that already has an Into.
It's not a problem you can "work around". It's a limitation imposed by the compiler to prevent future changes to your dependencies from subtly changing the behavior of your code.
For now, you avoid the error by implementing for concrete types instead of using generics and traits. A macro is one way to reduce the amount of keyboard entry you have to do.
In the future, some form of specialization might also be useful to solve this. However, this sits squarely in the middle of the reason that specialization isn't stable. It's possible to use this type of specialization to create unsound Rust using only safe code. A reduced form of specialization is being worked on, but it deliberately eschews the ability to specialize based on a trait and only works for concrete types.
See also:
Resolving trait implementation conflicts
Conflicting implementations of trait in Rust
How can I implement From for both concrete Error types and Box<Error> in Rust?
How is there a conflicting implementation of `From` when using a generic type?
I implemented a trait for another trait but cannot call methods from both traits
Based on the answers to the questions listed by Shepmaster, I have come up with the following workaround which appears to accomplish my mission. It is an implementation of the "use a macro to compactly do all the boilerplate of implementing the trait for a bunch of structs":
extern crate jni;
use jni::objects::{JObject, JValue};
use jni::JNIEnv;
pub trait ConvertRustToJValue<'a> {
fn into_jvalue(self, je: &JNIEnv<'a>) -> JValue<'a>;
}
macro_rules! impl_convert_rust_to_jvalue {
( $($t:ty),* ) => {
$( impl<'a> ConvertRustToJValue<'a> for $t
{
fn into_jvalue(self, _je:&JNIEnv) -> JValue<'a>
{
self.into()
}
}) *
}
}
impl_convert_rust_to_jvalue! { i8, i16, i32, i64 }

How can I add the to_string() functionality to an enum?

I am trying to create Error enums that implement to_string(). I have tried to derive(Debug) for them but it doesn't seem to be enough.
Here is the enum that I am working on:
#[derive(Debug, Clone)]
pub enum InnerError {
InnerErrorWithDescription(String),
}
#[derive(Debug, Clone)]
pub enum OuterError {
OuterErrorWithDescription(String),
}
What I am trying to make is:
// result type <T,InnerErrorWithDescription>
result.map_err(|err| { Error::OuterErrorWithDescription(err.to_string())}) // .to_string() is not available
I could not manage to convert InnerError enum type to OuterError.
What should I change to implement it?
I have made an example for writing enum types and their values' here:
Rust Playground
But, still I had to specify the type and it's description in match case, are there any more generic implementation?
Your enum should implement Display; from ToString docs:
This trait is automatically implemented for any type which implements
the Display trait. As such, ToString shouldn't be implemented
directly: Display should be implemented instead, and you get the
ToString implementation for free.
Edit: I have adjusted your playground example; I think you might be after something like this.

How to avoid Serde needing the Default trait for a field that skips serialization?

I have this struct that is created by deserializing with Serde and I want to have a field of type Url in it which is not deserialized:
#[derive(Deserialize, Debug)]
pub struct Flow {
#[serde(skip_deserializing)]
pub source: Url,
}
Playground
Serde complains about Url not satisfying the Default trait. I have tried with and without deriving Default. Is my only option for me to implement the Default trait for Url myself?
You can use #[serde(default = "path")] on the field to give a function with the signature fn() -> Url that should be called if the field is missing.
It's also possible to implement Deserialize yourself and handle missing values appropriately.

Implementing a trait that has associated trait types

I'm having trouble learning about associated types. My problem code:
trait Fooer {
fn foo(&self);
}
trait FooStore {
type T: Fooer;
fn store_foo(&self, fooer: Self::T);
}
#[allow(dead_code)]
struct DB {}
impl FooStore for DB {
type T = Fooer;
fn store_foo(&self, _fooer: Self::T) {}
}
fn main() {}
Play link
The intent here is to use associated types to make the FooStore trait not require the awkward and problematic syntax of impl<F:Fooer, T: FooStore<F>> FooStore<F> for DB because that often complains about F not being used.
However, the official docs on this feature show objects implementing the underlying associated type - but not traits. In this example, DB does not know what structs might be passed into store_foo(..), so it needs to use a trait to solve this issue.
With that said, how can I get an associated type to use a trait during impl? That is, how can I write type T = Fooer;? Or am I using this wrong somehow?
Note: I'm having some trouble constructing this example, I'm trying to correct this now. The error I was having is:
cargo: the trait `Fooer` cannot be made into an object [E0038]
The intent here is to use associated types to make the FooStore trait not require the awkward and problematic syntax of impl<F:Fooer, T: FooStore<F>> FooStore<F> for DB because that often complains about F not being used.
Your struct DB needs to assign a concrete type that implements Fooer to FooStore::T. Fooer is a trait, but can also be used as an unsized type. However, you can't use an unsized type here, because you can't pass an argument of an unsized type by value (which FooStore::store_foo requires).
If you don't want DB to assign a particular type to FooStore::T, then you can make DB generic.
use std::marker::PhantomData;
#[allow(dead_code)]
struct DB<F: Fooer> {
_phantom: PhantomData<F>,
}
impl<F: Fooer> FooStore for DB<F> {
type T = F;
fn store_foo(&self, _fooer: Self::T) {}
}
Notice the use of PhantomData: we use it to force the parameter T to be used, and it also indicates that DB<T> conceptually owns objects of type T.

Resources