将工作代码提取到单独的函数中时的生命周期问题
Lifetime problem when working code is extracted into a separate function
我正在编写一个程序来从日志文件(文本格式)中提取信息。整体流量
- 将文件逐行读入
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
}
我得到但不理解的错误是:
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
实际上只是用来获取列的名称,然后用于在[中查找值=22=] 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
:我认为这会解决问题,但字符串分配的数量可能会增加 20 倍,我想避免这种情况.当前程序速度惊人!
我怀疑问题实际上出在那个 UniCase<&'t str>
上,我需要为密钥提供它自己的生命周期。不知道如何。
所以我的问题是
- 为什么我不能轻松地将此代码移动到新函数中?
- 我该如何解决?
我明白这是一个相当长的问题。在本地使用代码 fiddle 可能更容易。它在 Github 上,错误应该可以重现:
git clone https://github.com/PhilipDaniels/log-file-processor
git checkout 80158b3
cargo build
从 process_line
调用 make_output_record
将推断出 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
是您的 return 值和参数的共同生命周期。这就是为什么您的代码无法正常工作的原因,因为 'p, 't,'c 对于参数和您的 return 值并不常见。
我在 here 中简化了您的代码,这是工作版本,如果您将其他生命周期参数添加回 make_output_record
,您可以恢复错误。
我正在编写一个程序来从日志文件(文本格式)中提取信息。整体流量
- 将文件逐行读入
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
}
我得到但不理解的错误是:
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
实际上只是用来获取列的名称,然后用于在[中查找值=22=] 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
:我认为这会解决问题,但字符串分配的数量可能会增加 20 倍,我想避免这种情况.当前程序速度惊人!
我怀疑问题实际上出在那个 UniCase<&'t str>
上,我需要为密钥提供它自己的生命周期。不知道如何。
所以我的问题是
- 为什么我不能轻松地将此代码移动到新函数中?
- 我该如何解决?
我明白这是一个相当长的问题。在本地使用代码 fiddle 可能更容易。它在 Github 上,错误应该可以重现:
git clone https://github.com/PhilipDaniels/log-file-processor
git checkout 80158b3
cargo build
从 process_line
调用 make_output_record
将推断出 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
是您的 return 值和参数的共同生命周期。这就是为什么您的代码无法正常工作的原因,因为 'p, 't,'c 对于参数和您的 return 值并不常见。
我在 here 中简化了您的代码,这是工作版本,如果您将其他生命周期参数添加回 make_output_record
,您可以恢复错误。