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

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.