The way to maintain mapping relations is match?

I have a thousand structs that implement MessageFull + Serialize. both those traits are object-unsafe

#[derive(serde::Serialize)]
struct MsgA {}
impl ::protobuf::Message for MsgA{}

and now I need to maintain a mapping relation that maps a u32 to one of these structs and execute fn like this.

fn from_bytes<T: MessageFull + Serialize>(v: Vec<u8>) -> String {
    let b = T::parse_from_bytes(&v).unwrap();
    let str = serde_json::to_string_pretty(&b).unwrap();
    str
}

the way to maintain the map is only match?

fn map_u32_to_msg(cmd: u32, v: Vec<u8>) {
    match cmd {
        1 => {
            to_bytes::<MsgA>(v);
        },
        2 => {
            to_bytes::<MsgB>(v);
        },
        // ... thousand times
        1000 => {
              to_bytes::<Msg1000>(v);
        }
        _ => {}
    }
}

My problem is similar to this one。link

if a trait is not object-safe, it is possible for specific use cases to create object-safe adapter traits. for example, Serialize trait is not object-safe, but you can create a object-safe version of it like this:

/// contrary to `serde::Serialize`, this trait is object-safe for a concrete serializer
trait MySerialize<S: Serializer> {
	fn serialize(&self, serializer: S) -> Result<S::Ok, S::Error>;
}
/// blanket implementation can be used to generate vtables which just forwards to the implementation of the non-object-safe trait
impl<T, S> MySerialize<S> for T
where
	T: Serialize,
	S: Serializer,
{
	fn serialize(&self, serializer: S) -> Result<S::Ok, S::Error> {
		<Self as Serialize>::serialize(self, serializer)
	}
}

for your specific use case of json, you can give a concrete serializer type and use dynamic dispatch on the trait objects. (actually, this is just an example to explain the method, you can define the adapter trait directly for serde_json::Serializer and not use generics at all)

/// this is an example of concrete serializer type, can use whatever serializers
/// suppose I want to serialize to files and use the compat formatter
type JsonSerializer = serde_json::Serializer<std::fs::File, serde_json::ser::CompactFormatter>;

/// the adapter object-safe trait
trait SerializeToJson {
	fn serialize_to_json(&self, serializer: &mut JsonSerializer) -> Result<(), serde_json::Error>;
}

/// forwarding blanket implementation
impl<T: Serialize> SerializeToJson for T {
	fn serialize_to_json(&self, serializer: &mut JsonSerializer) -> Result<(), serde_json::Error> {
		self.serialize(serializer)
	}
}
fn main() {
	let values: Vec<Box<dyn SerializeToJson>> = vec![
		Box::new(Foo::new()),
		Box::new(Bar::new()),
		Box::new(Baz::new()),
	];
}

I'm not familiar with the probobuf thing, but you can use similar trick like the Serialize example. the adapter trait doesn't need to be "complete", you just need to be able to implement for your specific use case.

3 Likes

you are saying pruning it to conform to object-safe trait?

I see MessageDyn probably stands for this

 let mut b: Box<dyn MessageDyn> = t.descriptor_dyn().parse_from_bytes(&v).unwrap();

on object-saft trait MessageDyn I can parse bytes to Box<dyn MessageDyn>, now how do I let MessageDyn and SerializeToJson work together? It seems impossible to Serialize a MessageDyn

fn from_dyn_bytes<T: MessageDyn + Serialize>(v: Vec<u8>, t: &T) -> String {
    let mut b: Box<dyn MessageDyn> = t.descriptor_dyn().parse_from_bytes(&v).unwrap();
    let str = serde_json::to_string_pretty(&b).unwrap();
    str
}

get the error: the trait bound dyn MessageDyn: Serialize is not satisfied

the way I define the serializer adapter, you need to manually create json serializers using writers, basically you create your own convenient serialize function similar to serde_json::to_writer(), to_vec(), or to_string().

but that's not the point: the serializer example is just an example to demonstrate the technique, I'm not saying you should use this exact design.

by adapter traits, what I really mean is, in order to use dynamic dispatch, you can create your own object-safe trait that can be implemented on generic types with the non-object-safe trait bounds, I'm not suggesting you need to make one-to-one corresponding traits for every non-object-safe trait.

take the from_bytes() function in OP as an example, you don't need to create a DynSerializeJson and DynParseProtobuf, and use these trait object in your code. you can just create whatever trait that suits your use case, e.g. a ProtobufToJson trait can be defined like this:

/// in order for this trait to be object safe, a `&self` parameter is needed
/// you can use a dummy type as a carrier, whose sole purpose is to hold the vtable
/// note I don't mention the non-object-safe traits when defining the trait,
/// instead I use them as bounds on the blanket implementation instead
trait ProtobufToJson {
  fn from_bytes(&self, v: &[u8]) -> String;
}

/// the dummy handle type, here I made it invariant, but you can have different design
struct Converter<T>(PhantomData<fn(T) -> T>);

/// this is the same code from OP
impl<T> ProtobufToJson for Converter<T> where T: MessageFull + Serialize {
  fn from_bytes(&self, v: &[u8]) -> String {
    let t = T::parse_from_bytes(v).unwrap();
    let s = serde_json::to_string_pretty(&t).unwrap();
    s
  }
}

then you can use it like this:

// assume `converters` is a collection of trait objects, e.g. Vec or HashMap etc.
let mut converters: Vec<Box<dyn ProtobufToJson>> = todo!();

// add trait objects into the collection:
converters.push(Box::new(Converter::<MsgA>::new()));
converters.push(Box::new(Converter::<MsgB>::new()));
converters.push(Box::new(Converter::<Msg10000>::new()));

// I don't know your use case, but suppose somehow you picked an implementation using app specific logic at runtime
let json_string = converters.iter().find(app_specific_logic).unwrap().from_bytes(&protobuf_bytes);
2 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.