Issue with returning tuple where creation of one element borrows other element

Hey everyone

I am using rexiv2 to read out metadata and embedded thumbnails of raw image files.

For that I have a function that given src &PathBuf extracts rexiv2::Metadata and Vec<rexiv2::PreviewImage> of that src. It then returns the tuple (rexiv2::Metadata, rexiv2::PreviewImage), where the second element is one of the extracted thumbnails.

I am having issues on making the code for this function syntactically correct. I think the difficulty is that preview extraction function (rexiv2::Metadata::get_review_images) borrows metadata, yielding issues when I trying to return metadata at the end of the function (but I may be completely wrong with my assumption).
What makes things more complicated is that neither rexiv2::Metadata nor rexiv2::PreviewImage implements Clone.

Even with the normally really helpful compiler messages, I was unable to resolve the issue.

I hope someone can point out the issue I am facing and hint me towards the proper way to implement the function.

Please see the attacked MVE and the compiler output below.

Thanks a lot!

MVE:

use std::path::PathBuf;

use rexiv2::{Metadata, PreviewImage};

/// Get image `src`s `Metadata` and `PreviewImage` with the highest resolution
fn get_metadata_preview(src: &PathBuf) -> (Metadata, PreviewImage) {
    assert!(src.is_absolute());

    println!("Processing image {:?}", src);

    let metadata = Metadata::new_from_path(src).unwrap();
    let previews: Vec<PreviewImage> = metadata.get_preview_images().unwrap();

    // // Get preview with highest resolution
    let largest_preview: PreviewImage = previews
        .into_iter()
        .max_by_key(|e| e.get_size())
        .expect("previews is not supposed to be empty");

    (metadata, largest_preview)
}

fn main() {
    let paths = vec![PathBuf::from("/tmp/bla1"), PathBuf::from("/tmp/bla2")];
    for src in paths.iter() {
        let (metadata, preview) = get_metadata_preview(src);

        println!("Got metadata {:?} and preview {:?}", metadata, preview);
    }
}

Compiler Ouput:

$ cargo check
    Checking thumbnail v0.1.0 (/home/jeanclaude/Documents/Programming/Test/Rust/thumbnail)
error[E0515]: cannot return value referencing local variable `metadata`
  --> src/main.rs:20:5
   |
12 |     let previews: Vec<PreviewImage> = metadata.get_preview_images().unwrap();
   |                                       ----------------------------- `metadata` is borrowed here
...
20 |     (metadata, largest_preview)
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^ returns a value referencing data owned by the current function

error[E0505]: cannot move out of `metadata` because it is borrowed
  --> src/main.rs:20:6
   |
6  | fn get_metadata_preview(src: &PathBuf) -> (Metadata, PreviewImage) {
   |                              - let's call the lifetime of this reference `'1`
...
11 |     let metadata = Metadata::new_from_path(src).unwrap();
   |         -------- binding `metadata` declared here
12 |     let previews: Vec<PreviewImage> = metadata.get_preview_images().unwrap();
   |                                       ----------------------------- borrow of `metadata` occurs here
...
20 |     (metadata, largest_preview)
   |     -^^^^^^^^------------------
   |     ||
   |     |move out of `metadata` occurs here
   |     returning this value requires that `metadata` is borrowed for `'1`

Some errors have detailed explanations: E0505, E0515.
For more information about an error, try `rustc --explain E0505`.
error: could not compile `thumbnail` (bin "thumbnail") due to 2 previous errors

You are absolutely spot on. Self-referential constructions are best avoided in Rust, they tend to be incompatible with the borrow checker.

Don't try to do all this in one step / one function. Make a function to return the metadata, then pass the metadata into a second function to get the largest preview.

3 Likes

Thank you very much. This makes sense to me. It works perfectly fine when splitting it into multiple functions as you suggested