Я пишу программу для извлечения информации из файлов журналов (в текстовом формате).Общий поток
- Строковое считывание файла в
String
- Создание структуры
ParsedLine
, которая заимствует несколько строковых фрагментов из этой строки (некоторые с использованием Cow
) - Используйте
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