Returning a vector of references

Hi, newbie here.

I started programming in Rust recently and I've been struggling for a bit trying to resolve this problem. Sorry if it was answered before but I couldn't find a general answer to this issue.

What I trying to understand is how to process some data in a function and return references to that data in Rust. For example - see the code below - there's a struct called World that keeps a vector of Shape structs. The function find will return a vector to those Shape structs that match certain criteria. In this case is y > 0 but it can be whatever.

This is the code:

struct Shape {
    pub x: i64,
    pub y: i64,
}

struct World {
    shapes: Vec<Shape>
}

impl World {
    pub fn find_<'a>(self) -> Vec<&'a Shape> {
        let result : Vec<&Shape> = self.shapes.iter().filter(|s| s.y > 0).collect();
        result
    }
}

fn main() {
    let s1 = Shape { x: 0, y: 0 };
    let s2 = Shape { x: 0, y: -1};

    let world = World { shapes: vec![s1, s2]};

    let result = world.find();

    for shape in result {
        print!("x: {}, y: {}", shape.x, shape.y);
    }
}

It fails with the error:

 bin/borrow.rs:13:9
   |
12 |         let result : Vec<&Shape> = self.shapes.iter().filter(|s| s.y > 0).collect();
   |                                    ----------- `self.shapes` is borrowed here
13 |         result
   |         ^^^^^^ returns a value referencing data owned by the current function

I think I understand why this happens (the references created by the iterator will be deleted when the function is exited) but I'm not sure how to resolve this in Rust for a general case. Another scenario could be when you have a struct that is a graph and you want to find the shortest path to one node to another. You might want a vector that contains the nodes that compose that path.

Although this can work copying the structs instead of having references, I'd like to know if this makes sense in Rust. I'm thinking that it can be useful when there are complex structs that can store tons of data, when the expected result can be large (e.g, a path composed by thousands of nodes), or when working with threading or async where the state of the structs can me modified by several actors.

Thanks for your help!

Your find_ method currently takes self by value due to how there is no ampersand on self. This means that the World object is destroyed by calling the method. The reason that it fails to compile is that the references you are returning pointed into the World object that has now been destroyed, but references must point at a valid value.

To fix this, change find to borrow the World rather than consume it:

impl World {
    pub fn find<'a>(&'a self) -> Vec<&'a Shape> {
        let result : Vec<&Shape> = self.shapes.iter().filter(|s| s.y > 0).collect();
        result
    }
}

Note that the above lifetimes are the default lifetimes you get if you don't write anything, so you can also write this as:

impl World {
    pub fn find(&self) -> Vec<&Shape> {
        let result : Vec<&Shape> = self.shapes.iter().filter(|s| s.y > 0).collect();
        result
    }
}
1 Like

Thank you! It makes perfect sense. I don't know why I thought self wasn't affected for the ownership rules :man_facepalming: .

So, taking this into account, in which cases make sense to move self instead of passing it by reference? I understand that, if World implements Copy there are cases where it makes sense, for example, something like pub fn size(self) -> usize, but if not, does it make sense at all?

It can make sense to take it by value if you want ownership of something inside it. For example, you could define a method that destroys the World object, returning the shapes vector without cloning the vector or the values inside it.

In cases when the object will be unusable after calling this method. The simplest example might probably be JoinHandle::join, since after it returns, the thread referenced by handle is already destroyed, so it doesn't make sense to have the handle afterwards.

Another general use case is transforming an object (e.g. with From), where you don't necessarily want to clone the contained data. Or other destructuring-related operations like for loops over owned values, powered by IntoIterator.

Thanks for your comments!!! I learnt a lot today.