Search code examples
rustlifetime

How to fix lifetime issue in Rust?


In summary, I want to have a function that digests a CSV file and depending on the existing values, it should decide with on_conflict if the current value or the new value should be used. However, I get the following error when I try to implement the function:

error[E0597]: `original_contents` does not live long enough
  --> src/utils/result_writer.rs:66:17
   |
58 | fn get_updated_contents<'old, 'new, 'out>(
   |                         ---- lifetime `'old` defined here
59 |     original_contents: String,
   |     ----------------- binding `original_contents` declared here
...
66 |     let lines = original_contents.lines().skip(1);
   |                 ^^^^^^^^^^^^^^^^^ borrowed value does not live long enough
...
74 |                 temp_lines.push(on_conflict(&columns[descriptor_values.len()..columns.len()].to_vec(), &value_values).to_vec());
   |                                 ------------------------------------------------------------------------------------- argument requires that `original_contents` is borrowed for `'old`
...
88 | }
   | - `original_contents` dropped here while still borrowed
fn get_updated_contents<'old, 'new, 'out>(
    original_contents: String,
    descriptor_values: Vec<&str>,
    value_values: Vec<&str>,
    on_conflict: fn(&'old Vec<&'old str>, &'new Vec<&'new str>) -> &'out Vec<&'out str>,
) -> Vec<String>
    where 'old: 'out, 'new: 'out {

    let lines = original_contents.lines().skip(1);

    let temp_lines: Vec<Vec<&str>> = {
        let mut temp_lines = Vec::new();

        for line in lines {
            let columns: Vec<&str> = line.split(",").collect();
            if columns.starts_with(&descriptor_values) {
                temp_lines.push(on_conflict(&columns[descriptor_values.len()..columns.len()].to_vec(), &value_values).to_vec());
            } else {
                temp_lines.push(columns);
            }
        }

        temp_lines.sort_by(compare_lines);
        temp_lines
    };

    temp_lines
        .iter()
        .map(|line| line.join(","))
        .collect()
}

Solution

  • I fixed it by using Higher-Rank Trait Bounds.

    fn get_updated_contents(
        old_contents: &str,
        descriptor_values: &[&str],
        value_values: &[&str],
        on_conflict: for<'a> fn(&'a [&'a str], &'a [&'a str]) -> &'a [&'a str],
    ) -> Vec<String> {
        let old_lines: Vec<Vec<&str>> = old_contents
            .lines()
            .skip(1)
            .map(|line| line.split(",").collect())
            .collect();
    
        let new_lines: Vec<Vec<&str>> = {
            let mut temp_lines: Vec<Vec<&str>> = Vec::new();
            let mut found = false;
    
            for i in 0..old_lines.len() {
                let columns = &old_lines[i];
                if columns.starts_with(&descriptor_values) {
                    found = true;
                    let mut new_line: Vec<&str> = Vec::new();
                    for i in 0..descriptor_values.len() {
                        new_line.push(descriptor_values[i]);
                    }
    
                    let old_values: &[&str] = &columns[descriptor_values.len()..columns.len()];
                    let result = on_conflict(old_values, value_values);
                    for value in result {
                        new_line.push(&value);
                    }
                    temp_lines.push(new_line);
                } else {
                    temp_lines.push(columns.clone());
                }
            }
    
            if !found {
                let mut new_line = Vec::new();
                new_line.extend_from_slice(&descriptor_values);
                new_line.extend_from_slice(&value_values);
                temp_lines.push(new_line);
            }
    
            temp_lines.sort_by(compare_lines);
            temp_lines
        };
    
        new_lines
            .iter()
            .map(|line| line.join(","))
            .collect()
    }