Capturing a vector inside a closure

Hello,
I have a Vec of paths and one of patterns, I feed the paths to WalkDir flatten the result, and then im trying to filter the WalkDir paths that matches at least one patterns:

ub struct Config<'a> {
    pub paths: Vec<&'a str>,
    pub patterns: Vec<&'a str>,
}

pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
    let patterns: Vec<_> = config.patterns.into_iter().unique().collect();
    let paths_ok = config
        .paths
        .into_iter()
        .map(WalkDir::new)
        .into_iter()
        .flatten()
        .filter_map(|path| path.ok());

    let paths_that_match = paths_ok.into_iter().filter(|entry| {
        if let Some(p) = entry.path().to_str() {
            !patterns
                .into_iter()
                .filter(|pattern| p.contains(pattern))
                .collect::<Vec<_>>()
                .is_empty()
        } else {
            false
        }
    });
    for p in paths_that_match {
        println!("{}", p.path().display());
    }
    Ok(())
}

It give me this error:

cannot move out of `patterns`, a captured variable in an `FnMut` closure

move occurs because `patterns` has type `std::vec::Vec<&str>`, which does not implement the `Copy` trait

For what Im able to understand from the error, it's like like if the pattern Vec is moved inside the closure, and in the second iteration of the filter adaptor is already consumed, I can also be wrong in my interpretation.

I need to Iterate one Vec over each element or the other.

Any Idea about How to do this?

Thanks in advance

It's a really simple thing to fix:

patterns.into_iter()

will take self by value, consuming the Vec, while

patterns.iter()

will take self by reference (&self), allowing you to keep the Vec.

If you wanted to mutate the elements you'd say:

patterns.iter_mut()

which will take self by mutable (AKA unique) reference (&mut self).

4 Likes

Of course, thanks

now its giving me this error:

expected a `std::ops::Fn<(char,)>` closure, found `str`

expected an `Fn<(char,)>` closure, found `str`

help: the trait `std::ops::Fn<(char,)>` is not implemented for `str`
note: required because of the requirements on the impl of `std::ops::FnOnce<(char,)>` for `&str`
note: required because of the requirements on the impl of `std::str::pattern::Pattern<'_>` for `&&&str`

For what i could find contains receives a Pattern and its saying that pattern is not implemented for str??
I guess that im not seeing something.

in the end i modify the code to use fold.

let paths_that_match = paths_ok.into_iter().filter(|entry| {
        if let Some(p) = entry.path().to_str() {
            patterns
                .iter()
                .fold(true, |acc, pattern| acc && p.contains(pattern))
        } else {
            false
        }
    });

It works !!!!
But could someone please explain me the previos error using filter
Thanks in advance

I recommend using find instead of fold as find will short circuit.

1 Like

As for the error, it's likely that since using iter does not take the vector by value, it has to add a reference to each value. Additionally when using filter, the closure cannot consume the value adding another reference. So your pattern had the type &&&str. Now if you look at the documentation for the Pattern trait, you will find that the implementors section contains:

impl<'a, 'b> Pattern<'a> for &'b str
impl<'a, 'b, 'c> Pattern<'a> for &'c &'b str

As well as some other things. So pattern accepts a &str and a &&str but not a &&&str and this is why it fails. You could fix it by using contains(*pattern) to remove one of the &s

1 Like

Hi,
You made me see a bug in my code, I the correct one should be:

let paths_that_match = paths_ok.into_iter().filter(|entry| {
        if let Some(p) = entry.path().to_str() {
            patterns
                .iter()
                .fold(false, |acc, pattern| acc || p.contains(pattern))
        } else {
            false
        }
    });

In the end i changed for find:

 let paths_that_match = paths_ok.into_iter().filter(|entry| {
        if let Some(p) = entry.path().to_str() {
            let is_found = patterns.iter().find(|pattern| p.contains(*pattern));
            if let Some(_) = is_found {
                true
            } else {
                false
            }
        } else {
            false
        }
    });

But the nested if/else are killing me, :weary:

Thanks for the tip.

You can use .is_some()

1 Like

You should be able to do something like this:

let paths_that_match = paths_ok.into_iter().filter(|entry| {
    if let Some(p) = entry.path().to_str() {
        patterns.iter().any(|pattern| p.contains(pattern))
    } else {
        false
    }
});

or going more functional:

let paths_that_match = paths_ok.into_iter().filter(|entry| {
    entry.path().to_str().map_or(false, |p| {
        patterns.iter().any(|pattern| p.contains(pattern))
    })
});
1 Like

OMG
Are Rust Iterator adapters infinite or something?

Virtually yes, you'll stop way before hitting the compiler's limit.

Also you don't really need paths_ok nor paths_that_match. I don't know if it's your actual code or a simplified version but what about this:

config
    .paths
    .into_iter()
    .map(WalkDir::new)
    .into_iter()
    .flatten()
    .filter_map(Result::ok)
    .filter(|entry| {
        entry.path().to_str().map_or(false, |p| {
            patterns.iter().any(|pattern| p.contains(pattern))
        })
    })
    .for_each(|p| println!("{}", p.path().display()));

You should test it though.

3 Likes

I tested, and it works
Thanks