1

I have a petgraph::Graph structure onto which I have imposed a tree structure by giving every node weight a parent_edge_idx which is an Option<EdgeIdx> of the edge that connects from its parent to itself.

I need to iterate over a node's children. I need the edge weight of the connecting edge and the node weight of the child.

I wanted to factor that iteration into a helper function that returns a reference to an Iterator<Item = (EdgeIdx, NodeIdx)>. I want to do this cost-free; since I have to borrow self.search_tree in order to do this, the iterator is only valid for the lifetime of self.

  1. Is this a reasonable function to want to write?
  2. Is it possible to write this function?

Any gated features are ok; I'm on nightly.

fn children<'a>(
    &'a mut self,
    node_idx: NodeIdx,
) -> &'a impl Iterator<Item = (EdgeIdx, NodeIdx)> {
    &self.search_tree.neighbors(node_idx).map(|child_idx| {
        let node = self.search_tree.node_weight(child_idx).unwrap();
        let edge_idx = node.parent_edge_idx.unwrap();
        (edge_idx, child_idx)
    })
}
mcarton
  • 27,633
  • 5
  • 85
  • 95
masonk
  • 9,176
  • 2
  • 47
  • 58
  • 2
    Try `impl Iterator + 'a` maybe? – Francis Gagné Mar 10 '18 at 21:11
  • 2
    If you are going to use advanced, unstable Rust features, you should probably already be familiar with the *basic* Rust language. You [shouldn't be attempting to return references to variables created in functions because it can never work](https://stackoverflow.com/q/32682876/155423). – Shepmaster Mar 11 '18 at 05:29
  • 1
    Please also provide a [MCVE] of your problem; doing so will generally get you better quality answers in a shorter timeframe. – Shepmaster Mar 11 '18 at 05:31
  • This isn't a duplicate question. The answer might be the same, but the question is different. This is important, e.g., because it's not likely to Google my question and find that answer. – masonk Mar 11 '18 at 05:41
  • As for minimal and complete - could you please elaborate? What part of my question is left out? I'm happy to update it. – masonk Mar 11 '18 at 05:49
  • 1
    @masonk Answers being the same *is* the important part of marking things as duplicates here on SO. When a question is marked as a duplicate, the question remains around forever, providing a signpost for those exact Google searches you mention. Those visitors end up here, can see if their question is close to yours (or the linked ones) and can then read the answers that are provided. – Shepmaster Mar 11 '18 at 18:56
  • 1
    The missing parts of the question include the entire definition of `Self`. The amount of code / work that you are requesting any potential answerer create out of thin air based on the code you have provided is immense — they have to guess at what all the types should be, what fields are present, what crates and exact imports are needed, they have to create a driver program and data to reproduce the error, etc. If you [edit] your question to contain such, I usually add a comment applying the duplicate(s) answers to your specific case when I close it. – Shepmaster Mar 11 '18 at 19:02
  • "The missing parts of the question include the entire definition of Self". Those parts of Self aren't relevant. All that matters, really, is that this function borrows self, and its return value can't outlive self. I will try to rephrase this more generally later though. I think the problem is that I did an X,Y. I wanted X, but I asked how to do Y. – masonk Mar 11 '18 at 20:13

1 Answers1

4

How to return an iterator is already covered in this question.

  1. Note that you don't need to return a reference: you want to return an iterator value directly, so if we remove the first & in both the method body and the return type, that's closer to what we need.

  2. We will use impl Iterator so that we don't have to name the actual iterator type exactly. Just note (code below) that we need to use the impl Iterator<..> + 'a syntax, which means that the (anonymous) iterator contains references valid to use for at least the lifetime 'a.

  3. We can't use &mut self here! Note that we need to borrow self.search_tree twice: once for the .neighbors() iterator and once for the self.search_tree that's used in the map closure. Multiple borrowing is incompatible with mutable references.

  4. We put move as the capture mode on the closure, so that it captures the self reference directly, and not by reference (this is important so that we can return the iterator and the closure.

  5. Petgraph specific, but we replace g.node_weight(node_index).unwrap() with just &g[node_index] which is equivalent, but the latter is easier to read.

Here is a reproduction of your code, but with modifications along 1-5 to make it compile:

#![feature(conservative_impl_trait)]
extern crate petgraph;

use petgraph::Graph;
use petgraph::graph::{NodeIndex, EdgeIndex};

struct Foo {
    search_tree: Graph<Node, i32>,
}

struct Node {
    parent_edge_idx: Option<EdgeIndex>,
}

impl Foo {
    fn children<'a>(&'a self, node_idx: NodeIndex)
        -> impl Iterator<Item = (EdgeIndex, NodeIndex)> + 'a
    {
        self.search_tree.neighbors(node_idx).map(move |child_idx| {
            let node = &self.search_tree[child_idx];
            let edge_idx = node.parent_edge_idx.unwrap();
            (edge_idx, child_idx)
        })
    }
}
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
bluss
  • 12,472
  • 1
  • 49
  • 48
  • Thank you bluss! I'm confused, because I thought that only references could have lifetimes, but it looks like you're showing an owned type with a lifetime here. Shepmaster is definitely right that I've missed something fundamental about Rust lifetimes which I have to go back and figure out. – masonk Mar 11 '18 at 20:20
  • @masonk `Box` and `impl Iterator` let's you “forget” about the specific type behind the iterator.. but there is one thing that Rust will never let you forget about.. how long that value is valid to use! (Lifetimes!). That's the short story of it. – bluss Mar 11 '18 at 20:39