rustpetgraph

How can I store a type directly in a struct when all the traits I need are implemented for references to that type?


Petgraph implements all its traits for references to it's internal graph type. How can I store and use a Graph rather than an &Graph in a struct?

This works but stores a reference to the graph:

extern crate petgraph;

use crate::petgraph::visit::*;
use petgraph::data::*;
use petgraph::*;

struct StateMachine<G>
where
    G: GraphBase,
{
    state_network: G,
    state: <G as GraphBase>::NodeId,
}

impl<G> StateMachine<G>
where
    G: IntoNodeReferences
        + IntoEdgeReferences
        + IntoEdges
        + Data
        + NodeIndexable
        + GraphProp
        + DataMap
        + GraphBase,
    <G as Data>::NodeWeight: Eq + Copy,
    <G as Data>::EdgeWeight: Eq + Copy,
{
    pub fn next(&mut self, input: <G as Data>::EdgeWeight) -> Option<<G as Data>::NodeWeight> {
        for edge in self.state_network.edges(self.state) {
            if *edge.weight() == input {
                self.state = edge.target();
                return match self.state_network.node_weight(self.state) {
                    Option::Some(weight) => Some(*weight),
                    Option::None => Option::None,
                };
            }
        }
        return Option::None;
    }

    pub fn new(network: G, start: <G as Data>::NodeWeight) -> Option<StateMachine<G>> {
        for nr in network.node_references() {
            if *(network.node_weight(nr.id())).unwrap() == start {
                return Option::Some(StateMachine {
                    state_network: network,
                    state: nr.id(),
                });
            }
        }
        return Option::None;
    }
}

fn main() {
    let mut sn: Graph<&str, u32, petgraph::Directed> = Graph::new();
    let sn_item1 = sn.add_node("a");
    let sn_item2 = sn.add_node("b");
    let sn_item3 = sn.add_node("c");
    let sn_item4 = sn.add_node("d");
    let sn_item5 = sn.add_node("e");
    sn.add_edge(sn_item1, sn_item2, 1);
    sn.add_edge(sn_item1, sn_item3, 2);
    sn.add_edge(sn_item2, sn_item4, 1);
    sn.add_edge(sn_item2, sn_item5, 2);
    sn.add_edge(sn_item5, sn_item1, 2);
    sn.add_edge(sn_item5, sn_item3, 1);
    let mut sm = StateMachine::new(&sn, "a").unwrap();
    println!("{}", sm.next(1).unwrap());
}

Link to rust playground

But storing the Graph directly doesn't work

extern crate petgraph;

use crate::petgraph::visit::*;
use petgraph::data::*;
use petgraph::*;

struct StateMachine<'a, G>
where
    &'a G: GraphBase,
{
    state_network: G,
    state: <&'a G as GraphBase>::NodeId,
}

impl<'a, G> StateMachine<'a, G>
where
    &'a G: IntoNodeReferences
        + IntoEdgeReferences
        + IntoEdges
        + Data
        + NodeIndexable
        + GraphProp
        + DataMap
        + GraphBase,
    <&'a G as Data>::NodeWeight: Eq + Copy,
    <&'a G as Data>::EdgeWeight: Eq + Copy,
{
    pub fn next(&mut self, input: <&'a G as Data>::EdgeWeight) -> Option<<&'a G as Data>::NodeWeight> {
        for edge in (&self.state_network).edges(self.state) {
            if *edge.weight() == input {
                self.state = edge.target();
                return match (&self.state_network).node_weight(self.state) {
                    Option::Some(weight) => Some(*weight),
                    Option::None => Option::None,
                };
            }
        }
        return Option::None;
    }

    pub fn new(network: G, start: <&'a G as Data>::NodeWeight) -> Option<StateMachine<'a, G>> {
        for nr in network.node_references() {
            if *((&network).node_weight(nr.id())).unwrap() == start {
                return Option::Some(StateMachine {
                    state_network: network,
                    state: nr.id(),
                });
            }
        }
        return Option::None;
    }
}

fn main() {
    let mut sn: Graph<&str, u32, petgraph::Directed> = Graph::new();
    let sn_item1 = sn.add_node("a");
    let sn_item2 = sn.add_node("b");
    let sn_item3 = sn.add_node("c");
    let sn_item4 = sn.add_node("d");
    let sn_item5 = sn.add_node("e");
    sn.add_edge(sn_item1, sn_item2, 1);
    sn.add_edge(sn_item1, sn_item3, 2);
    sn.add_edge(sn_item2, sn_item4, 1);
    sn.add_edge(sn_item2, sn_item5, 2);
    sn.add_edge(sn_item5, sn_item1, 2);
    sn.add_edge(sn_item5, sn_item3, 1);
    let mut sm = StateMachine::new(sn, "a").unwrap();
    println!("{}", sm.next(1).unwrap());
}

Link to rust playgound

  Compiling playground v0.0.1 (/playground)
error[E0495]: cannot infer an appropriate lifetime for borrow expression due to conflicting requirements
  --> src/main.rs:29:21
   |
29 |         for edge in (&self.state_network).edges(self.state) {
   |                     ^^^^^^^^^^^^^^^^^^^^^
   |
note: first, the lifetime cannot outlive the anonymous lifetime #1 defined on the method body at 28:5...
  --> src/main.rs:28:5
   |
28 | /     pub fn next(&mut self, input: <&'a G as Data>::EdgeWeight) -> Option<<&'a G as Data>::NodeWeight> {
29 | |         for edge in (&self.state_network).edges(self.state) {
30 | |             if *edge.weight() == input {
31 | |                 self.state = edge.target();
...  |
38 | |         return Option::None;
39 | |     }
   | |_____^
note: ...so that reference does not outlive borrowed content
  --> src/main.rs:29:21
   |
29 |         for edge in (&self.state_network).edges(self.state) {
   |                     ^^^^^^^^^^^^^^^^^^^^^
note: but, the lifetime must be valid for the lifetime `'a` as defined on the impl at 15:6...
  --> src/main.rs:15:6
   |
15 | impl<'a, G> StateMachine<'a, G>
   |      ^^
   = note: ...so that the expression is assignable:
           expected <&G as petgraph::visit::GraphBase>::NodeId
              found <&'a G as petgraph::visit::GraphBase>::NodeId

error[E0495]: cannot infer an appropriate lifetime for borrow expression due to conflicting requirements
  --> src/main.rs:32:30
   |
32 |                 return match (&self.state_network).node_weight(self.state) {
   |                              ^^^^^^^^^^^^^^^^^^^^^
   |
note: first, the lifetime cannot outlive the anonymous lifetime #1 defined on the method body at 28:5...
  --> src/main.rs:28:5
   |
28 | /     pub fn next(&mut self, input: <&'a G as Data>::EdgeWeight) -> Option<<&'a G as Data>::NodeWeight> {
29 | |         for edge in (&self.state_network).edges(self.state) {
30 | |             if *edge.weight() == input {
31 | |                 self.state = edge.target();
...  |
38 | |         return Option::None;
39 | |     }
   | |_____^
note: ...so that reference does not outlive borrowed content
  --> src/main.rs:32:30
   |
32 |                 return match (&self.state_network).node_weight(self.state) {
   |                              ^^^^^^^^^^^^^^^^^^^^^
note: but, the lifetime must be valid for the lifetime `'a` as defined on the impl at 15:6...
  --> src/main.rs:15:6
   |
15 | impl<'a, G> StateMachine<'a, G>
   |      ^^
   = note: ...so that the expression is assignable:
           expected <&G as petgraph::visit::GraphBase>::NodeId
              found <&'a G as petgraph::visit::GraphBase>::NodeId

error: aborting due to 2 previous errors

For more information about this error, try `rustc --explain E0495`.
error: could not compile `playground`.

To learn more, run the command again with --verbose.

Solution

  • I am not familiar with petgraph and its concepts now. From my experience storing references in structs almost always ends with complete rewrites so my approach would have been something like this:

    struct StateMachine<G, E, N>
    where
      G: GraphBase<EdgeId = E, NodeId = N>,
      E: Copy + PartialEq,
      N: Copy + PartialEq,
    {
      state_network: G,
      state: N,
    }
    
    impl<G, E, N, EW, NW> StateMachine<G, E, N>
    where
      G: Data<NodeWeight = NW, EdgeWeight = EW>
        + NodeIndexable
        + GraphProp
        + DataMap
        + GraphBase<EdgeId = E, NodeId = N>,
      E: Copy + PartialEq,
      N: Copy + PartialEq,
      for<'a> &'a G: IntoNodeReferences
        + IntoEdgeReferences
        + IntoEdges
        + Data<NodeWeight = NW, EdgeWeight = EW>
        + GraphBase<EdgeId = E, NodeId = N>,
      EW: Eq + Copy,
      NW: Eq + Copy,
    {
      pub fn next<'a, 'b: 'a>(&'a mut self, input: EW) -> Option<NW> {
        for edge in (&self.state_network).edges(self.state) {
          if *edge.weight() == input {
            self.state = edge.target();
            return match self.state_network.node_weight(self.state) {
              Option::Some(weight) => Some(*weight),
              Option::None => Option::None,
            };
          }
        }
        return Option::None;
      }
    
      pub fn new(
        network: G,
        start: NW,
      ) -> Option<StateMachine<G, <G as GraphBase>::EdgeId, <G as GraphBase>::NodeId>> {
        let mut found = Option::None;
    
        for nr in network.node_references() {
          if *(network.node_weight(nr.id())).unwrap() == start {
            found = Option::Some(nr.id());
          }
        }
        found.map(|id| StateMachine {
          state_network: network,
          state: id,
        })
      }
    }
    

    EdgeId and NodeId get generic parameters (E,N)since these are reused in the implementation of StateMachine, NodeWeight and EdgeWeight also get generic (NW, EW) but only to join the associated types of &Graph and Graph.