1

I am using Serde to deserialize BSON objects to Rust struct instances. I can deserialize the objects to concrete struct instances, but how can I deserialize generically?

I have "countries" and "cities" collections in MongoDB. In the Rust program, I have a struct for Country and City. When I pull a country or a city from Mongo, I can deserialize it using Serde to the Country or City struct. See the second line in main() below.

I want to deserialize the BSON object into a generic Location object. Based on what I read about generics in the Rust book, I created a trait LocationTrait and implemented it for Country and City. (see line 3 in main()). It fails to compile, saying the size for values of type dyn LocationTrait cannot be known at compilation time.

#[derive(Serialize, Deserialize)]
pub struct Country {
    pub name: String,
}

#[derive(Serialize, Deserialize)]
pub struct City {
    pub name: String,
}

pub trait LocationTrait {}
impl LocationTrait for Country {}
impl LocationTrait for City {}

fn main() {
    let item = mongo_coll
        .find_one(Some(doc! {"name": "usa"}), None)
        .unwrap()
        .unwrap();
    let country: Country = bson::from_bson(bson::Bson::Document(item)).unwrap();
    // fails -> let gen_location: LocationTrait = bson::from_bson(bson::Bson::Document(item)).unwrap();
}

Eventually, I would like to create a generic object that represents Country or a City. But, I am not sure of the starting point -- do I need to focus on a trait or do I need to create a new trait-bound struct?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
deltavin
  • 295
  • 1
  • 2
  • 6

1 Answers1

1

There are two issues preventing your code from compiling.

The first error you saw: the size for values of type dyn LocationTrait cannot be known at compilation time, is due to the fact that bson::from_bson needs to return the result of the deserialization by value. The compiler needs to know how much space it needs to allocate in the call stack to return it.

However traits are an abstraction to describe behaviour and not data, so it could be implemented for an u8 (a single byte) or much larger struct.

In order to be able to return such value, you would need to box it (see Trait Objects).

The second issue is that the return value must implement the Deserialize trait (and not LocationTrait)

To solve these issue:

The simplest path is to use enums instead of traits:

#[derive(Serialize, Deserialize)]
#[serde(tag = "type")]
pub enum Location {
    Country(Country),
    City(City)
}

This will work with documents such as {"type" = "Country", name="usa"}. Check the Serde doc for more options.

If you really want to use traits (e.g. to be able to define types outside of this module), you will need a boxed trait and a custom structs as such:

// The same trait as defined earlier
pub trait LocationTrait {}
impl LocationTrait for Country {}
impl LocationTrait for City {}

// A custom struct on which you can implement the deserialize trait
// Needed as both Deserialize and Box are defined outside this crate.
struct DynLocation(Box<dyn LocationTrait>);

impl<'de> Deserialize<'de> for DynLocation {
    fn deserialize<D>(_deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        // Tricky part ommited here:
        // You will need to partially deserialize you object
        // in order to get a first discriminant before instanciating
        // and deserializing the proper type.
        unimplemented!()
    }
}

// The public method to hide the DynLocation wrapper
pub fn deserialize(item: &str) -> Box<dyn LocationTrait> {
    let location: DynLocation = serde_json::from_str(item).expect("invalid json");
    location.0
}

Some discussion around this same topic can be found in How can deserialization of polymorphic trait objects be added in Rust if at all?.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
kimsnj
  • 131
  • 1
  • 4
  • This explains it well. Thanks, especially for the enum idea, which seems to be making more sense for this situation. – deltavin Jan 14 '19 at 02:51