pchampin / sophia_rs

Sophia: a Rust toolkit for RDF and Linked Data
Other
210 stars 23 forks source link

the trait `sophia::graph::Graph` cannot be made into an object #122

Closed KonradHoeffner closed 1 year ago

KonradHoeffner commented 1 year ago

I want my application to work with both FastGraph and LightGraph, so that users can choose which suits their usecase best, but I cannot use Graph parameters because it is not "object safe" according to the compiler.

fn foo<T, E>(g: &(dyn Graph<Triple=T,Error=E> + 'static)) {}
cargo check                                                                                                                                     
    Checking [...]
error[E0038]: the trait `sophia::graph::Graph` cannot be made into an object
   --> src/rdf.rs:109:23
    |
109 | fn foo<T, E>(g: &(dyn Graph<Triple=T,Error=E> + 'static)) {
    |                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `sophia::graph::Graph` 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>
   --> /home/konrad/.cargo/registry/src/github.com-1ecc6299db9ec823/sophia_api-0.7.2/src/graph/_traits.rs:90:8
    |
90  |     fn triples_with_s<'s, TS>(&'s self, s: &'s TS) -> GTripleSource<'s, Self>
    |        ^^^^^^^^^^^^^^ the trait cannot be made into an object because method `triples_with_s` has generic type parameters
...
99  |     fn triples_with_p<'s, TP>(&'s self, p: &'s TP) -> GTripleSource<'s, Self>
    |        ^^^^^^^^^^^^^^ the trait cannot be made into an object because method `triples_with_p` has generic type parameters

Is there some way around this? Are all those generic parameters needed?

pchampin commented 1 year ago

This is indeed a limitation of the Graph trait, and I do not plan to change it in the near future (this would break too many things).

What prevents you from using a fully polymorphic function instead?

fn foo<G: Graph>(g: G) {}
KonradHoeffner commented 1 year ago

This works perfectly, thanks! I did not know this is possible, as I started with fn foo(g : Graph) as I would in for example Java and then I just followed the directions of the borrow checker starting with using "dyn".

KonradHoeffner commented 1 year ago

Unfortunately, I am again at a point where not being able to use it as a Trait object makes my code very convoluted. Depending on the runtime parameters (e.g. extension of the file to be loaded) of my program, different types of Graph are used and the resulting graph is stored in an enum:

pub enum GraphEnum {
    FastGraph(FastGraph),
    #[cfg(feature = "hdt")]
    HdtGraph(HdtGraph),
}

Now I have to create a helper method for every method that uses a graph, for example:

pub fn serialize_rdfxml(iri: Iri<&str>) -> Result<String, Box<dyn Error>> {
    match graph() {
        GraphEnum::FastGraph(g) => serialize_rdfxml_generic(g, iri),
        #[cfg(feature = "hdt")]
        GraphEnum::HdtGraph(g) => serialize_rdfxml_generic(g, iri),
    }
}

pub fn serialize_rdfxml_generic<G: Graph>(g: &G, iri: Iri<&str>) -> Result<String, Box<dyn Error>> {
    Ok(RdfXmlSerializer::new_stringifier().serialize_triples(g.triples_matching(Some(deskolemize(&iri)), Any, Any))?.to_string())
}

The code is now littered with those helper methods, and adding anything has become cumbersome. I have tried to write an "Impl" block for GraphEnum with a triples_matching method, because that is the only one I am using, but that did not seem work because the GTripleSource trait is very complex as well. Is there anything else I can do to simplify this?

KonradHoeffner commented 1 year ago

P.S.: After several hours, I figured out the way given below, in case someone else needs this functionality. I think the amount of complicated traits like these make using Sophia a bit cumbersome sometimes, but I guess that is a different issue so I will close this issue again.

#[allow(clippy::large_enum_variant)]
pub enum GraphEnum {         
    FastGraph(FastGraph),
    #[cfg(feature = "hdt")]
    HdtGraph(HdtGraph),
}                      

impl GraphEnum {
    fn triples_matching<'s, S, P, O>(&'s self, sm: S, pm: P, om: O) -> Box<dyn Iterator<Item = Result<[SimpleTerm<'static>; 3], Infallible>> + '_>
    where  
        S: TermMatcher + 's,
        P: TermMatcher + 's,
        O: TermMatcher + 's,
    {
        match self {
            // both graphs produce infallible results
            GraphEnum::FastGraph(g) => Box::new(g.triples_matching(sm, pm, om).flatten().map(|triple| Ok(triple.map(SimpleTerm::from_term)))),
            #[cfg(feature = "hdt")]
            GraphEnum::HdtGraph(g) => Box::new(g.triples_matching(sm, pm, om).flatten().map(|triple| Ok(triple.map(SimpleTerm::from_term)))),
        }                                                       
    }
}