Any way to avoid iterating by index?

Given the following case:

code
struct Collection {
    list: Vec<String>,
}

struct Similar<'c> {
    list: Vec<&'c str>
}

impl Collection {
    pub fn new() -> Self {
        Self { list: Vec::new() }
    }
    pub fn add(&mut self, text: String) {
        self.list.push(text);
    }
    pub fn try_add(&mut self, text: String) -> Result<(), Similar<'_>> {
        let mut similar = Vec::new();
        for item in self.list.iter() {
            if item.contains(&text) {
                similar.push(item.as_str());
            }
        }
        // if any similar/same items found, return them
        if !similar.is_empty() {
            return Err(Similar {
                list: similar
            })
        }
        // otherwise, add right away
        self.list.push(text);
        Ok(())
    } 
}

Is there any way to avoid:

compiler error
error[E0502]: cannot borrow `self.list` as mutable because it is also borrowed as immutable
  --> src/lib.rs:26:13
   |
12 |       pub fn try_add(&mut self, text: String) -> Result<(), Similar<'_>> {
   |                      - let's call the lifetime of this reference `'1`
13 |           let mut similar = Vec::new();
14 |           for item in self.list.iter() {
   |                       --------- immutable borrow occurs here
...
21 |               return Err(Similar {
   |  ____________________-
22 | |                 list: similar
23 | |             })
   | |______________- returning this value requires that `self.list` is borrowed for `'1`
...
26 |               self.list.push(text);
   |               ^^^^^^^^^^^^^^^^^^^^ mutable borrow occurs here

For more information about this error, try `rustc --explain E0502`.

Without resorting to an index-based workaround via .iter().enumerate() or equivalent? It seems to be a rather straightforward reference flow for the borrow checker to handle, yet none of my attempts seem to have worked so far.

Including those to manually drop the similar itself, self.list.iter() saved to a separate variable beforehand, or manual 0..self.list.len() iteration with borrowing by index.

1 Like

This is the classic borrow checker limitation around conditional return of borrows. If you take a borrow and then decide whether or not to return it, the compiler treats it as if it is always returned, or as if the borrow lasts to the end of the function body, even if it was actually dropped.

The workaround within the language is to arrange so that you do not take the borrow until after you are certain you are going to return it; in this case, you do need to use an index but you can do it just once to make the decision:

pub fn try_add(&mut self, text: String) -> Result<(), Similar<'_>> {
    if let Some(pos) = self.list.iter().position(|item| item.contains(&text)) {
        let mut similar = vec![self.list[pos].as_str()];
        for item in self.list[(pos + 1)..].iter() {
            if item.contains(&text) {
                similar.push(item.as_str());
            }
        }
        Err(Similar { list: similar })
    } else {
        self.list.push(text);
        Ok(())
    }
}

You can also use polonius-the-crab to handle cases where taking the borrow only after the decision is not possible.

6 Likes

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.