将工作代码提取到单独的函数中时的生命周期问题

时间:2018-12-20 12:16:14

标签: rust lifetime borrowing

我正在编写一个程序来从日志文件(文本格式)中提取信息。总体流量是

  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
}

我得到但不理解的错误是:

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实际上仅用于获取列的名称,该列的名称随后用于在{{1 }} HashMap(kvps用于使查找不区分大小写)。如果找到一个值,则将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>> } :我认为这可以解决问题,但是String分配的数量可能会增加20倍,我想避免这种情况。当前的程序速度惊人!

我怀疑问题实际上出在Cows上,我需要给出密钥的寿命。不确定如何。

我的问题是

  • 为什么不能轻松地将此代码移到新函数中?
  • 我该如何解决?

我明白这是一个相当长的问题。在本地处理代码可能更容易。它在Github上,并且错误可以通过以下方式重现:

UniCase<&'t str>

1 个答案:

答案 0 :(得分:4)

make_output_recordprocess_line的调用将推断make_output_record的生存期参数。

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

这意味着'p是生命周期,所有者将在process_line的范围内生存(由于推断)。根据您的代码parsed_linecolumns居住在'p中。 'p是返回值和参数的通用生存期。这就是为什么您的代码无法正常工作的原因,因为'p,'t,'c对于参数和返回值并不常见。

我在here中简化了您的代码,这是有效的版本,如果您将其他生命周期参数添加回make_output_record,则可能会返回错误。