【问题标题】:Lifetime problem when working code is extracted into a separate function将工作代码提取到单独的函数中时的生命周期问题
【发布时间】:2019-05-20 23:11:51
【问题描述】:

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

  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实际上只用于获取列名,然后用于在kvpsHashMap中查找值(@987654334 @ 用于使查找不区分大小写)。如果找到值,我们将&amp;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&lt;&amp;'t str&gt; 上,我需要将密钥赋予它自己的生命周期。不知道如何。

所以我的问题是

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

我很欣赏这是一个相当长的问题。在本地修改代码可能更容易。它在 Github 上,错误应该可以通过以下方式重现:

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

【问题讨论】:

  • 请提供您遇到的问题的minimal, complete, verifable example。从代码中删除并非绝对需要的所有依赖项(例如结构或枚举)。另请阅读mcve related advices for rust
  • 缺少太多代码,但我认为问题可能出在 ``` let ci_comparer = UniCase::new(column.name.as_str()); match parsed_line.kvps.get(&ci_comparer) { ``` 您的 UniCase 包含来自该列的引用,因此 then 会告诉您不能返回悬空引用。但这只是一个猜测。请提供完整的代码示例。
  • Cow&lt;'t, str&gt;UniCase&lt;&amp;'t str&gt; 都阻止您编写此函数,因为您无法从其中任何一个获得 &amp;'t strCow 问题是 Cow 所固有的,但我觉得 Unicase 的 API 在这种情况下比它需要的更尴尬,如果你有足够的决心,你可以解决它。

标签: rust lifetime borrowing


【解决方案1】:

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_linecolumns生活在'p'p 是您的返回值和参数的共同生命周期。这就是您的代码无法正常工作的原因,因为 'p, 't ,'c 对于参数和返回值并不常见。

我在here 中简化了您的代码,这是工作版本,如果您将其他生命周期参数添加回make_output_record,您可以恢复错误。

【讨论】:

  • 谢谢!那解决了它。你的解释也帮助我以比现在更全面的方式思考人生。
猜你喜欢
  • 2021-02-12
  • 1970-01-01
  • 2015-11-15
  • 2013-03-27
  • 2020-12-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多