2

i want to read a file randomly from Vec<String> and then use it as multipart to reqwest async

but i keep getting: borrowed value doesn't live long enough

here's the function

fn getFiles(dirname: &str) -> Vec<String> {
    let mut items: Vec<String> = Vec::new();
    let dir = std::fs::read_dir(dirname);
    for item in dir.expect("fail") {
        if let Ok(item) = item {
            items.push(item.path().into_os_string().into_string().unwrap());
        }
    }
    items
}
    // call the function
    let dir_items = getFiles("src/assets");
    let file = dir_items.into_iter().choose(&mut rand::thread_rng()).unwrap();
    let path = std::path::Path::new(&file);
    let sub_file = std::fs::read(path)?;
    // after this, file lifetime is already end ?
    let sub_file_part = reqwest::multipart::Part::bytes(sub_file)
        .file_name(path.file_name().unwrap().to_string_lossy())
        .mime_str("application/octet-stream")?;

playground

Francis Gagné
  • 60,274
  • 7
  • 180
  • 155
bren
  • 69
  • 8
  • I am not familiar with `rust`, but the SO & Google are. Did you read [Value does not live long enough](https://stackoverflow.com/questions/42503296/value-does-not-live-long-enough) – Luuk Aug 08 '21 at 07:53
  • 1
    [Playground link with a bit of additional stuff so that it compiles (or at least fails where it should)](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=794ef871fd563d3acddd70a2ff9d7c01) – Elias Holzmann Aug 08 '21 at 08:38

1 Answers1

2

Let's look at this piece by piece:

let path = std::path::Path::new(&file);

If you have a look at the documentation for Path::new, you see the following signature:

pub fn new<S: AsRef<OsStr> + ?Sized>(s: &S) -> &Path

After applying the second lifetime elision rules, (quote: "if there is exactly one input lifetime parameter, that lifetime is assigned to all output lifetime parameters: fn foo<'a>(x: &'a i32) -> &'a i32"), this signature really looks like this:

pub fn new<'a, S: AsRef<OsStr> + ?Sized>(s: &'a S) -> &'a Path

So, your path variable cannot live longer than your file variable.

Next, this is relevant:

.file_name(path.file_name().unwrap().to_string_lossy())

After again applying the lifetime elision rules, you can see that calling unwrap on path.file_name() gives you a &std::ffi::OsStr that can live no longer than your path variable which in turn (as discussed before) can live no longer than your file variable. So, transitively, path.filename().unwrap() can live no longer than the file variable.

Now, let's have a look at the signature of OsStr::to_string_lossy():

pub fn to_string_lossy(&self) -> Cow<'_, str>

So, this method returns a Cow<'_, str>. Cow is shorthand for "Copy or Owned" – meaning that the Cow can either contain a reference to the data (in this case, a &str, if the OsStr contained only valid UTF-8 bytes) or it can own the data itself (in this case, by containing a String, if the OsStr contained data that needed to be converted or to be filtered out during UTF-8 conversion).

'_ is a placeholder and means that the compiler shall (again) infer the lifetime with the lifetime elision rules. The expanded signature looks like this:

pub fn to_string_lossy<'a>(&'a self) -> Cow<'a, str>

So, in your example, 'a can't be any bigger than the lifetime of your &OsStr returned by unwrap(), which transitively means that 'a can't be bigger than the lifetime of file.

However, reqwest::multipart::Part::file_name() expects something implementing Into<Cow<'static, str>> as parameter. Our Cow<'a, str> definitively can't implement that, as our file variable is not alive until the end of the program. If it was alive until the end of the program, our Cow would implement it because file could be borrowed for 'static, and all would be well – this is the reason for the error message.

You can work around this by calling into_owned() on the Cow. This converts the Cow into a string, which does implement Into<Cow<'static, str>>:

use rand::prelude::*;

fn getFiles(dirname: &str) -> Vec<String> {
    let mut items: Vec<String> = Vec::new();
    let dir = std::fs::read_dir(dirname);
    for item in dir.expect("fail") {
        if let Ok(item) = item {
            items.push(item.path().into_os_string().into_string().unwrap());
        }
    }
    items
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // call the function
    let dir_items = getFiles("src/assets");
    let file = dir_items.into_iter().choose(&mut rand::thread_rng()).unwrap();
    let path = std::path::Path::new(&file);
    let sub_file = std::fs::read(path)?;
    // after this, file lifetime is already end ?
    let sub_file_part = reqwest::multipart::Part::bytes(sub_file)
        .file_name(path.file_name().unwrap().to_string_lossy().into_owned())
        .mime_str("application/octet-stream")?;
    Ok(())
}

Playground

Elias Holzmann
  • 3,216
  • 2
  • 17
  • 33
  • @bren Yeah, lifetimes can be quite tricky to get right (and sometimes give less than helpful error messages). But it gets easier with more experience ;-) – Elias Holzmann Aug 08 '21 at 10:43