使用函数参数 Vec<String> 的值创建结构并将 Vec<struct> 返回给调用者

Creating struct with values from function parameter Vec<String>and returning Vec<struct> to caller

我的程序的目的是从文件中读取 questions/answers(逐行),并从中创建几个 struct,放入 Vec 以供进一步处理.

我有一段相当长的代码,我试图将其分成几个函数(Playground 上的完整版本;希望是有效的 link)。

我想我对借贷、生命周期和其他事情了解不多。除此之外,我看到的周围的例子,我无法适应我给定的问题。

尝试将我的结构字段从 &str 重塑为 String 没有任何改变。就像在 get_question_list.

中创建 Vec<Question>

关注的函数如下:

fn get_question_list<'a>(mut questions: Vec<Question<'a>>, lines: Vec<String>) -> Vec<Question<'a>> {

    let count = lines.len();

    for i in (0..count).step_by(2) {

        let q: &str = lines.get(i).unwrap();
        let a: &str = lines.get(i + 1).unwrap();

        questions.push(Question::new(q, a));

    }

    questions

}

此代码因编译器而失败,如下(摘录):

error[E0597]: `lines` does not live long enough
   --> src/main.rs:126:23
    |
119 | fn get_question_list<'a>(mut questions: Vec<Question<'a>>, lines: Vec<String>) -> Vec<Question<'a>> {
    |                      -- lifetime `'a` defined here
...
126 |         let a: &str = lines.get(i + 1).unwrap();
    |                       ^^^^^ borrowed value does not live long enough
127 | 
128 |         questions.push(Question::new(q, a));
    |         ----------------------------------- argument requires that `lines` is borrowed for `'a`
...
163 | }
    | - `lines` dropped here while still borrowed

打给get_question_list的时间是:


    let lines: Vec<String> = content.split("\n").map(|s| s.to_string()).collect();

    let counter = lines.len();

    if counter % 2 != 0 {
        return Err("Found lines in quiz file are not even (one question or answer is missing.).");
    }

    questions = get_question_list(questions, lines);

    Ok(questions)

问题是你的 Questions 应该借用一些东西(因此生命周期注释),但是 lines 得到 moved 到函数中,所以当你从一行中创建一个新问题时,它会借用 函数局部数据 ,它将在函数结束时被销毁。因此,您创建的问题无法逃脱创建它们的函数。

现在你可以做的是不将lines移动到函数中:lines: &[String]将使调用者拥有这些行,这将“修复”get_question_list.

然而 read_questions_from_file 中存在完全相同的问题,并且无法解决:lines 是从文件中读取的,因此必然是函数的局部(除非你移动mainread_questions_from_file 的行读也只借用了它们)。

因此,最简单的正确修复方法是将 Question 更改为 拥有 其数据:

struct Question {
    question: String,
    answer: String
}

这样问题本身就可以保持其数据存在,问题就会消失。

我认为我们可以进一步改进:

首先,我们可以使用 String::lines 去除换行符周围的整个混乱,它将处理跨平台换行符,并将去除它们.

get_question_list 仅按值获取向量以附加到它并立即 return 它似乎也很奇怪。更直观的界面是:

  1. 采用 &mut 的“输出向量”,以便调用者可以预先确定大小或在多个负载中重用它,这在这种情况下似乎没什么用
  2. 或者在内部创建输出向量,这似乎是这里最明智的情况

这是我认为更令人满意的版本:https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=c0d440d67654b92c75d136eba2bba0c1

fn read_questions_from_file(filename: &str) -> Result<Vec<Question>, Box<dyn Error>> {
    let file_content = read_file(filename)?;
    let lines: Vec<_> = file_content.lines().collect();
    if lines.len() % 2 != 0 {
        return Err(Box::new(OddLines));
    }

    let mut questions = Vec::with_capacity(lines.len() / 2);
    for chunk in lines.chunks(2) {
        if let [q, a] = chunk {
            questions.push(Question::new(q.to_string(), a.to_string()))
        } else {
            unreachable!("Odd lines should already have been checked");
        }
    }
    Ok(questions)
}

请注意,我内联/删除了 get_question_list,因为我认为此时它没有发挥作用,而且它既琐碎又非常具体。

这里有一个变体,它的工作原理类似但有不同的权衡:https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=3b8f95aef5bcae904545617749086dbc

fn read_questions_from_file(filename: &str) -> Result<Vec<Question>, Box<dyn Error>> {
    let file_content = read_file(filename)?;
    let mut lines = file_content.lines();
    
    let mut questions = Vec::new();
    while let Some(q) = lines.next() {
        let a = lines.next().ok_or(OddLines)?;
        questions.push(Question::new(q.to_string(), a.to_string()));
    }

    Ok(questions)
}

它避免将 lines 收集到 Vec,但结果在它知道该文件合适之前必须处理文件到最后,并且它不能预分配 Questions.

在这一点上,因为我们不再关心 linesVec,我们可以对 BufRead 进行操作并删除 read_file

fn read_questions_from_file(filename: &str) -> Result<Vec<Question>, Box<dyn Error>> {
    let file_content = BufReader::new(File::open(filename)?);
    let mut lines = file_content.lines();
    
    let mut questions = Vec::new();
    while let Some(q) = lines.next() {
        let a = lines.next().ok_or(OddLines)?;
        questions.push(Question::new(q?, a?));
    }

    Ok(questions)
}

额外的?是因为虽然str::Lines产生&strio::Lines产生Result<String, io::Error>:尝试读取时延迟报告IO错误,这意味着如果 read_to_string 失败,则每行读取都可能报告失败。

OTOH 因为 io::Lines return 是 Result<String, ...> 我们可以直接使用 qa 而无需将它们转换为 String