Understanding a compiler error

Can anybody help me with understanding the following compiler error?

error[E0596]: cannot borrow `*drop_list` as mutable, as it is behind a `&` reference
  --> src/main.rs:96:54
   |
96 |         path_list = path_list.into_iter().filter(|p| drop_list.any(|d| p.contains(d))).collect()
   |                                                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ cannot borrow as mutable

path_list is a Vec<String>
drop_list is a Iterator<Item = &'a str>

The drop_list should be kept and not mutated in any way.

To check if any item in the Iterator satisfy the given condition, you have to iterate over it. This is, obviously, mutating operation.
However, this code does not show why drop_list is borrowed at all. Could you share more complete example?

1 Like

Sure, here it is:

fn map_path<'a, T: Iterator<Item = &'a str>>(path: &str, prefix: Option<&str>, drops: &Option<T>) -> String
{
    let mut path_list: Vec<String> = path.trim().trim_matches(':').split(':').map(|p| to_canonic(p)).collect();
    if let Some(drop_list) = drops {
        path_list = path_list.into_iter().filter(|p| !drop_list.any(|d| p.contains(d))).collect()
    }
    if let Some(prf) = prefix {
        let prf = &to_canonic(prf);
        let pvar = "${".to_owned() + PREFIX_VAR + "}";
        path_list = path_list.iter().map(|p| p.replace(prf, &pvar)).collect();
    }
    path_list.iter().fold(String::from(""), |path, p| path + ":" + &p).trim_start_matches(':').to_string()
}

I don't see why drop_list should be mutable when checking a predicate on the elements.

The &Option<T> argument only gives an immutable view of the iterator. However in order to do any iteration, you’ll need to be able to advance and hence mutate an iterator.

Maybe you want some different kinds of bounds, perhaps you don’t want T to be an iterator but a collection where &T implements IntoIterator. Something like this compiles:

fn map_path<T>(path: &str, prefix: Option<&str>, drops: &Option<T>) -> String
where
    for<'a> &'a T: IntoIterator<Item = &'a str>,
{
    let mut path_list: Vec<String> = vec![];
    if let Some(drop_list) = drops {
        path_list = path_list.into_iter().filter(|p| !drop_list.into_iter().any(|d| p.contains(d))).collect()
    }
    
    todo!()
}

If you want to be able to pass iterators instead, then you’d have to be able to e.g. Clone the iterator in order to be able to iterate multiple times. Or pass an owned Option<T> and collect the thing into a Vec first in order to be able to iterate multiple times, etc. If you’re unsure, feel free to list some types that you plan on using for T when calling this function.

1 Like

Ok, thanks a lot for the explanation and suggested remedy. I think I got the point now.

I think I'll go for the clone() solution, the drops iterator comes directly from clap:

let drops = cli_args.values_of("drop");

Note that the iterator in question seems to be checking UTF-8-validity of the arguments as it’s iterating. If you clone the iterator, every clone will redo the same validity checking. It might be beneficial to, instead, collect the items into a Vec<&str> once, and then re-use that Vec. Arguably this may be premature optimization where it doesn’t matter, so ultimately do what you like best; however using a Vec might even make the code simpler, too.

Ok, thanks for the input. I ended up with initially producing an Option<Vec<&str>> from the iterator and take an Option<&[&str]> as argument. Now the function looks like this:

/// Map path components
///
/// The path is assumed to consist of components separated by `:`. Components are first canonicalized using [to_canonic], then nonexistent components and those matching drop fragments are dropped,
/// then fragment replacements are applied, and finally variable values are replaced with `${`VAR`}` and prefix is replaced by the `${`[PREFIX_VAR]`}`
///
/// # Argument
/// * `path` String slice representing a list of file system paths separated by `:`
/// * `drops` Optional vector of path fragments. Paths containing these will be dropped
/// * `replacements` Optional vector of path fragment replacements
/// * `vars` Optional variable to path fragment mapping, including prefix
/// # Returns
/// String containing the mapped path.
fn map_path(path: &str,
            drops: Option<&[&str]>,
            replacements: Option<&[Replacement]>,
            vars: &Option<BTreeMap<&str, &str>>,
            check_path: bool) -> String
{
    use std::path::Path;
    let mut path_list: Vec<String> = path.trim().trim_matches(':').split(':').map(to_canonic).collect();
    if check_path {
        path_list = path_list.into_iter().filter(|p| Path::new(p).exists()).collect()
    }
    if let Some(drop_list) = drops {
        path_list = path_list.into_iter().filter(|p| !drop_list.iter().any(|d| p.contains(d))).collect()
    }
    if let Some(replacement_list) = replacements {
        path_list = path_list.into_iter().map(|p| replacement_list.iter().fold(p, |path, r| path.replace(r.0, r.1))).collect()
    }
    if let Some(vars_map) = vars {
        path_list = path_list.into_iter().map(|mut p| {
            vars_map.iter().for_each(|(var, val)| p = p.replace(val, &(String::from("${") + var + "}")));
            p
        }).collect()
    }
    path_list.into_iter().fold(String::from(""), |path, p| path + ":" + &p).trim_start_matches(':').to_string()
}

Hope that looks ok?

I'm thinking of replacing the function with all these Option<X> arguments with a single Transformer<T> struct that contains transformer functions f(x: T) -> T in a vector, and implements a trait with a transform(x: T)->T function that applies all these transformer functions in the order they appear in the vector. Probably this already exists, as it is a common problem?

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.