Жизненная проблема, когда рабочий код извлекается в отдельную функцию - PullRequest
0 голосов
/ 20 декабря 2018

Я пишу программу для извлечения информации из файлов журналов (в текстовом формате).Общий поток

  1. Строковое считывание файла в String
  2. Создание структуры ParsedLine, которая заимствует несколько строковых фрагментов из этой строки (некоторые с использованием Cow)
  3. Используйте ParsedLine для записи CSV-записи.

Пока все идет очень хорошо, но я столкнулся с проблемой, которую не понимаю,Я думаю, что это время жизни или анализ потока данных.Проблема в небольшом рефакторе, который я пытаюсь сделать.

У меня есть эта функция, которая работает:

fn process_line(columns: &[Column], line: String,  writer: &mut Writer<File>) {
    let parsed_line = ParsedLine::new(&line);

    if parsed_line.is_err() {
        let data = vec![""];
        writer.write_record(&data).expect("Writing a CSV record should always succeed.");
        return;
    }

    let parsed_line = parsed_line.unwrap();
    // let data = output::make_output_record(&parsed_line, columns);

    // The below code works. But if I try to pull it out into a separate function
    // Rust will not compile it.
    let mut data = Vec::new();

    for column in columns {
        match column.name.as_str() {
            config::LOG_DATE => data.push(parsed_line.log_date),
            config::LOG_LEVEL => data.push(parsed_line.log_level),
            config::MESSAGE => data.push(&parsed_line.message),

            _ => {
                let ci_comparer = UniCase::new(column.name.as_str());
                match parsed_line.kvps.get(&ci_comparer) {
                    Some(val) => {
                        let x = val.as_ref();
                        data.push(x);
                    },
                    None => data.push(""),
                }
            },
        }
    }

    writer.write_record(&data).expect("Writing a CSV record should always succeed.");
}

Но я хочу вытащитьфрагмент кода, который создает data в отдельную функцию, чтобы я мог ее легче протестировать. Вот функция:

pub fn make_output_record<'p, 't, 'c>(parsed_line: &'p ParsedLine<'t>, columns: &'c [Column]) -> Vec<&'t str> {
    let mut data = Vec::new();

    for column in columns {
        match column.name.as_str() {
            config::LOG_DATE => data.push(parsed_line.log_date),
            config::LOG_LEVEL => data.push(parsed_line.log_level),
            config::MESSAGE => data.push(&parsed_line.message),

            _ => {
                let ci_comparer = UniCase::new(column.name.as_str());
                match parsed_line.kvps.get(&ci_comparer) {
                    // This is the problem here. To make it explicit:
                    //     val is a "&'t Cow<'t, str>" and x is "&'t str"
                    Some(val) => {
                        let x = val.as_ref();
                        data.push(x);
                    },
                    None => data.push(""),
                }
            },
        }
    }

    data
}

И ошибку я получаю и не понимаюis:

error[E0623]: lifetime mismatch                                                                                                                                                                                      
--> src/main.rs:201:5                                                                                                                                                                                             
    |                                                                                                                                                                                                                
177 | pub fn make_output_record<'p, 't, 'c>(parsed_line: &'p ParsedLine<'t>, columns: &'c [Column]) -> Vec<&'t str> {                                                                                                
    |                                                                                 ------------     ------------                                                                                                  
    |                                                                                 |                                                                                                                              
    |                                                                                 this parameter and the return type are declared with different lifetimes...                                                    
...                                                                                                                                                                                                                  
201 |     data                                                                                                                                                                                                       
    |     ^^^^ ...but data from `columns` is returned here                                                                                                                                                           

Компилятор считает, что возвращаемый вектор содержит информацию из Columns, но Columns фактически используется только для получения имени столбца, который затем используется для поисказначение в kvps HashMap (UniCase используется, чтобы сделать поиск нечувствительным к регистру).Если значение найдено, мы добавляем &str к data.

Так что я не понимаю, почему компилятор считает, что что-то из Columns заканчивается в data, потому что, на мой взглядColumns - это просто часть метаданных, используемых для получения окончательного содержимого data, но сама по себе она не появляется в data.Как только поиск kvps завершен, и у нас может быть значение Columns, оно также может не существовать.

Я пробовал различные способы исправить это (включая добавление явных времен жизни ко всему, удаление некоторых времен жизни идобавление различных спецификаций времени жизни устаревших), но никакая комбинация не может сказать компилятору, что Columns не используется в data.

Для справки, вот определение ParsedLine:

#[derive(Debug, Default, PartialEq, Eq)]
pub struct ParsedLine<'t> {
    pub line: &'t str,
    pub log_date: &'t str,
    pub log_level: &'t str,
    pub message: Cow<'t, str>,
    pub kvps: HashMap<UniCase<&'t str>, Cow<'t, str>>
}

Обратите внимание, что я сопротивляюсь избавлению от Cows: я предполагаю, что это решит проблему, но число распределений String, вероятно, увеличится в 20 раз, и я бы хотел этого избежать.Текущая программа впечатляюще быстрая!

Я подозреваю, что проблема на самом деле с этим UniCase<&'t str>, и мне нужно дать ключу его собственное время жизни.Хотя не уверен, как.

Итак, мой вопрос

  • Почему я не могу легко переместить этот код в новую функцию?
  • Как мне это исправить?

Я ценю, что это довольно длинный вопрос.Это может быть легче возиться с кодом на местном уровне.Это на Github, и ошибка должна быть воспроизведена с помощью:

git clone https://github.com/PhilipDaniels/log-file-processor
git checkout 80158b3
cargo build

1 Ответ

0 голосов
/ 20 декабря 2018

Вызов make_output_record из process_line выведет параметр времени жизни make_output_record.

pub fn make_output_record<'p>(parsed_line: &'p ParsedLine, columns: &'p [Column]) -> Vec<&'p str> {

Это означает, что 'p - это время жизни, при котором владелец будет живым в области действия process_line (из-за логического вывода).По вашему коду parsed_line и columns живет в 'p.'p - это общее время жизни возвращаемого значения и аргументов.Вот почему ваш код не работал, потому что 'p,' t, 'c не является общим для аргументов и вашего возвращаемого значения.

Я упростил ваш код в здесь , это рабочая версия, вы можете вернуть ошибку, если добавите другие параметры времени жизни обратно к make_output_record.

...