Get item from vector passed by reference

Hi all. I should start by saying that I am new to Rust, so this question might be too obvious! :slight_smile:

Regardless, all I am doing is a function that receives a vector by reference and I just use find to find an element and then return it:

fn get_file_from_list(list: &Vec<ZippedFile>, filename: &str) -> ZippedFile {
    let result = list.iter().find(|&f| f.path == filename).unwrap();
    return result;
}

But the compiler then complains:

error[E0308]: mismatched types
  --> src/main.rs:78:12
   |
76 | fn get_file_from_list(list: &Vec<ZippedFile>, filename: &str) -> ZippedFile {
   |                                                                  ---------- expected `ZippedFile` because of return type
77 |     let result = list.iter().find(|&f| f.path == filename).unwrap();
78 |     return result;
   |            ^^^^^^ expected struct `ZippedFile`, found `&ZippedFile`

error: aborting due to previous error

For more information about this error, try `rustc --explain E0308`.

It says that the return type should be &ZippedFile and I don't understand why, and it doesn't seem to be the most correct thing to actually return &ZippedFile, I mean... the compiler doesn't even let you do that, while recommending doing this instead:

fn get_file_from_list<'a>(list: &'a Vec<ZippedFile>, filename: &'a str) -> &'a ZippedFile {
    let result = list.iter().find(|&f| f.path == filename).unwrap();
    return result;
}

But I must be honest and say that I don't undestand and don't know if the suggestion is the ideal thing or if there is a simpler way of doing this.

Thanks in advance!

This should be sufficient

fn get_file_from_list<'a>(list: &'a [ZippedFile], filename: &str) -> &'a ZippedFile {
    let result = list.iter().find(|&f| f.path == filename).unwrap();
    return result;
}
  1. You almost never want &Vec<T>, &[T] is more flexible
  2. Since there are multiple lifetimes in the arguments, you need to tell Rust what you want.
  3. The ZippedFile comes from the slice, so you need to annotate the slice reference and the output reference with the same lifetime (in this case 'a)
  4. The filename doesn't need to be bound to the output, because a ZippedFile doesn't come from filename, it comes from the slice
2 Likes

Welcome! There are a few ways to solve your problem:

Returning a clone

In Rust, most values can't be copied implicitly. If you want to return a copy of an item in the Vec, it has to implement the Clone trait, and then you can use the clone method to make a copy:

fn get_file_from_list(list: &Vec<ZippedFile>, filename: &str) -> ZippedFile {
    let result = list.iter().find(|&f| f.path == filename).unwrap();
    result.clone()
}

Now, after your function returns, the caller will have two copies of the same ZippedFile: The one in the Vec, which is no longer borrowed, and the clone that got returned.

Returning a reference

Cloning a value might be expensive, if it contains a lot of data. If that's the case, you can avoid cloning by returning a reference, like in your second example. Then, after the function returns, the caller has a reference to a ZippedFile that still lives in the Vec. While this reference exists, the Vec will remain borrowed, ensuring that it can't be modified and the reference will remain valid.

Side note: You don't need to keep the filename borrowed for the lifetime of the returned reference, so you can simplify the function signature slightly:

fn get_file_from_list<'a>(list: &'a [ZippedFile], filename: &str) -> &'a ZippedFile {
    list.iter().find(|f| f.path == filename).unwrap()
}

Removing the item from the Vec

Another way to avoid cloning the ZippedFile is to remove it from the Vec before returning it. To do this, you'll need mutable access to the Vec. Afterward, the caller will have only one copy of the desired ZipFile, since the Vec will no longer contain it:

fn get_file_from_list(list: &mut Vec<ZippedFile>, filename: &str) -> ZippedFile {
    let i = list.iter().position(|f| f.path == filename).unwrap();
    list.swap_remove(i) // fast, but changes the order of remaining items
}

Consuming the Vec

If you only need to use the Vec once, you can take it by value instead of borrowing it, and then use the into_iter method which consumes (destroys) the Vec and lets you move items out of it:

fn get_file_from_list(list: Vec<ZippedFile>, filename: &str) -> ZippedFile {
    list.into_iter().find(|f| f.path == filename).unwrap()
}

This again avoids cloning, but after the function returns, the caller won't be able to use the original Vec at all, because it has been consumed!

4 Likes

Thank you so much, to both of you @mbrubeck and @RustyYato.
I wish I could mark both as possible solutions :slight_smile:

But now I understand the limitations I am facing and the options I have.

For now, I guess I will go through the cloning, but I will explore the idea of returning a reference later as to improve the general performance and memory usage of the program I am writing.

Once again, thank you very much!

1 Like

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.