What is the right way to manipulate vec elements?


#1

I build a Vec and then want to process those elements. Processing means changing the result field of IndividualTestResults type. I’ve danced around all kinds of errors between mutability and borrowing and can’t seem to make progress. I’m guessing this is one of those learning curve things where vec in rust aren’t like lists in other languages.

So my question is how can I change result field of an IndividualTestResults instance in vector?
eg:

let test_name: String = str2string!("test_one");
let mut failed : TestSet = TestSet::new();
let test = IndividualTestResults { name : test_name.to_string(), result : str2string!("")};
failed.push(test);
failed.update_results(test_name, str2string!("new results text));  

Here’s my record type:

pub struct IndividualTestResults {
    pub name : String,
    pub result : String
}

impl PartialEq for IndividualTestResults {
    fn eq(&self, other: &IndividualTestResults) -> bool {
        return self.name == other.name;
    }

    fn ne(&self, other: &IndividualTestResults) -> bool {
        return self.name != other.name;
    }
}

and here’s my collection, based on Vec:

pub type TestSet = Vec<IndividualTestResults>;

pub trait TestSetFunctions {
    fn new() -> TestSet;
    fn find_by_name(&self, test_name : &String) -> &IndividualTestResults;
    fn index_of(&self, test_name: &String) -> usize;
    fn update_results(&mut self, test_name: &String, test_results: &String);
}

impl TestSetFunctions for TestSet {
    fn new() -> TestSet {
        return Vec::new();
    }

    fn find_by_name(&self, test_name : &String) -> &IndividualTestResults {
        for test in self {
            if test_name.to_string() == test.name {
                return test;
            }
        }
        panic!(format!("{} not found.", test_name))
    }

    fn index_of(&self, test_name: &String) -> usize {

        for index in 0..self.len() {
            if test_name.to_string() == self[index].name {
                return index;
            }
        }

        panic!(format!("{} not found.", test_name))
    }

    fn update_results(&mut self, test_name: &String, test_results: &String) {
        let test = self.find_by_name(test_name);     **// immutable borrow occurs here....?**

        let new_test: IndividualTestResults = IndividualTestResults {
            name: test.name.to_string(),
            result : test_results.to_string(),
        };

        let index = self.index_of(test_name);
        self.remove(index);
        self.push(new_test);

    }
}

The errors I get are:
let test = self.find_by_name(test_name); // immutable borrow occurs here…?
self.push(new_test); // mutable borrow occurs here

I apologize this is such a mess. I dug myself into a mess on this one.
Thnx
Matt


#2

I think with non-lexical lifetimes – nightly #![feature(nll)] – this will just work, but in the meantime you should be able to scope the borrow returned from find_by_name, something like:

let new_test: IndividualTestResults = { // new scope
    let test = self.find_by_name(test_name); // immutable borrow

    IndividualTestResults {
        name: test.name.to_string(),
        result : test_results.to_string(),
    }
}; // borrow is released at the end of scope

let index = self.index_of(test_name);
self.remove(index);
self.push(new_test);

Or if the name is really all you need, then use a one-liner without binding the borrowed value:

let name = self.find_by_name(test_name).name.to_string();
let new_test: ...

#3

For this higher-level question, I’d say create a new method like

find_by_name_mut(&mut self, test_name: &String) -> &mut IndividualTestResults

and then you can just assign test.result = ...

Further aside, it’s almost always better to take a &str parameter, rather than &String, as it’s more flexible for the caller.


#4

That got me a little further along. A unit test passes. However, I’m struggling to understand it applies in this use case:

I have another type that contains the TestSet vec type, above:

pub struct OrganizedTestResults {
    pub success : TestSet,
    pub failed : TestSet,
    pub skipped : TestSet,
    pub total: i32
}

and then I have another type that acts on OrganizedTestResult like this:

pub struct ProcessIndividualTestResults {
}

impl ProcessIndividualTestResults {
    pub fn merge_test_errors_into_results(input : &Vec<String>, mut results : &OrganizedTestResults) {
          // we did stuff to find the test to update      
          results.failed.update_results(&test_name, &line);
      }
}

Compile results as follows:

144 |     pub fn merge_test_errors_into_results(input : &Vec<String>, mut results : &OrganizedTestResults) {
    |                                                                               --------------------- use `&mut OrganizedTestResults` here to make mutable
...
167 |                 results.failed.update_results(&test_name, &line);
    |                 ^^^^^^^^^^^^^^ cannot mutably borrow field of immutable binding

I’ve tried the compiler suggestion at line 144. It creates more compile errors on the same line.

Thnx
Matt


#5

Here, only the results binding itself is mutable, but the value is still behind an immutable reference. So the only mutation possible is to point results at some other &OrganizedTestResults reference, only affecting this local variable name.

So if you want to changed the referenced value, results: &mut OrganizedTestResults should do it. For your followup errors, I’m guessing you have more cases of &test_name or the like keeping the struct immutably borrowed?


#6

Thank you. I think I had some references/mut keywords wrong, probably from trying to solve the original problem at the top of the question. With your help, I have that correct now.

I appreciate you’re help.

Matt