How to deal with a generic wrapper <G> with bounds on G and &'a G?

I'm trying to create a wrapper on a graph of the petgraph library and have run into the following issue. Here is a minimum repoduction:

use petgraph::{
    data::Build,
    graph::UnGraph,
    visit::{IntoEdgeReferences, NodeIndexable},
    Graph,
};
use std::fmt::Debug;

struct Wrap<G>
where
    G: Build + IntoEdgeReferences + NodeIndexable,
    G::EdgeRef: Debug,
    G::EdgeWeight: Default,
{
    graph: G,
}

impl<G> Wrap<G>
where
    G: Build + IntoEdgeReferences + NodeIndexable,
    G::EdgeRef: Debug,
    G::EdgeWeight: Default,
{
    fn new(graph: G) -> Self {
        Self { graph }
    }

    fn insert_edge(&mut self, from: usize, to: usize) {
        let from = self.graph.from_index(from);
        let to = self.graph.from_index(to);
        self.graph.add_edge(from, to, Default::default());
    }

    fn iter_edges(&self) {
        self.graph.edge_references().for_each(|e| println!("{e:?}"));
    }
}

#[test]
fn test() {
    let graph: UnGraph<u32, u32> = UnGraph::new_undirected();
    let wrap = Wrap::new(graph);
}

So the issue is that Build is implemented on G, however IntoEdgeReferences is implemented on &'a G, something I have, up until now, very rarely seen. I have made various attempts to fix this issue such as specifying traits for the reference:

&'a G: IntoEdgeReferences,
<&'a G as IntoEdgeReferences>::EdgeRef: Debug,

or (attempting to) implement Borrow:

G: Borrow<H>,
H: IntoEdgeReferences

But I both feel that I am polluting my code with long, complicated trait bounds (much longer than in this example), and more importantly, find myself unsuccessful in compiling my code despite taking all advice from rust analyzer. Could someone give me some guidance on how I can handle a generic wrapper which needs bounds from owning, referenced and mutable references to the inner type?

You could try a higher-rank trait bound

for<'a> &'a G: IntoEdgeReferences

Or you could put the bound(s) only on the methods that need them, where the concrete lifetime is available / can be named then.

1 Like

Another thing to keep in mind is that you usually don't want to put bounds on the struct definition unless they're needed to define the layout of the structure (by, for example, including an associated type as a field).


This type annotation is only necessary for the HRTB solution. Bounding the method instead doesn't require it.

Edit: You can also fix this by defining new() in its own impl block that doesn't have the higher-ranked bounds.

2 Likes

Thanks for the help guys. My example didn't really fully clarify the issue as adding the &'a bound would make a huge amount of methods fail as they expected &'a G::Type rather than G::Type. In my previous attempts, I went around adding bounds on G types where they expected the &'a G type, which led nowhere good. I gave it another shot and this ended up solving most problems, with all others being solved by refactoring:

    for<'a> &'a G: IntoEdges<NodeId = G::NodeId>,
    for<'a> <&'a G as IntoEdgeReferences>::EdgeRef: Debug,

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.