How to adapt flatbuffers `table` to be used with `Cow` in `Rust`?

Viewed 156

I have some hierarchy of traits and i need to adapt both structs and flatbuffers table to be used via the trait as follows:

// trait
pub trait TFilter : Clone {
    fn get_text(&self) -> &str;
}

// structure

#[derive(Clone)]
pub struct FilterStruct {
    pub text: String
}

impl TFilter for FilterStruct {
    fn get_text(&self) -> &str {
        &self.text
    }
}

impl TFilter for FilterTable<'_> {
    fn get_text(&self) -> &str {
        self.text()
    }
}

flatbuffers schema (IDL):

table FilterTable {
    text: string (required);
}

It works fine with the following usage:

pub fn accept_filter(filter: &dyn TFilter) {
    println!("{}", filter.get_text());
}

    accept_filter(&FilterStruct {
        text: "hello".to_owned()
    });

The problem happens when i inheritance is introduced: a new trait that extends the base trait:

pub trait TActiveFilter {
    fn get_filter(&self) -> Cow<dyn TFilter>;
    fn get_title(&self) -> &str;
}

a new structure that extends the base struct ("extend by containing"):

#[derive(Clone)]
pub struct ActiveFilterStruct {
    pub filter: FilterStruct,
    pub title: String
}

impl TActiveFilter for ActiveFilterStruct {
    fn get_filter(&self) -> Cow<dyn TFilter> {
        Cow::Borrowed(&self.filter)
    }
    fn get_title(&self) -> &str {
        &self.title
    }
}

and a new flatbuffers table that extends the base table:

table ActiveFilterTable {
    filter: FilterTable (required);
    title: string (required);
}
impl TActiveFilter for ActiveFilterTable<'_> {
    fn get_filter(&self) -> Cow<dyn TFilter> {
        Cow::Owned(self.filter())
    }

    fn get_title(&self) -> &str {
        self.title()
    }
}

The actual problem is that flatbuffer's generated Rust file returns instances (not references) and one has to own it, so using the Cow (struct already own and returns the reference but flatbuffer's impl has to own the result).

However with Cow i get the following:

error[E0038]: the trait `TFilter` cannot be made into an object
  --> src/wrapper.rs:76:30
   |
76 | pub fn accept_filter(filter: Cow<dyn TFilter>) {
   |                              ^^^^^^^^^^^^^^^^ `TFilter` 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>
  --> src/wrapper.rs:5:21
   |
5  | pub trait TFilter : Clone {
   |           -------   ^^^^^ ...because it requires `Self: Sized`
   |           |
   |           this trait cannot be made into an object...

What exactly must be made?

1 Answers

Trait objects can not be cloned since their size is unknown. If you take a look at Clone and fn clone(&self) -> Self you will see that it returns Self, in this case dyn TFilter. Cow<T> requires the Clone bound on T, otherwise it wouldn't be possible to get an owned instance of T.

It's the same error that you will get when trying to pass a raw trait object to a function, e.g.:

trait Foo {}
fn foo(_ :dyn Foo) {

}

You might be able to work around this by returning Box<dyn TFilter> from your get_filter method, rather than the Cow. Following another approach, you could try to stick to static dispatch rather than trait objects in the Cow.

Related