使用函数参数 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
是从文件中读取的,因此必然是函数的局部(除非你移动main
和 read_questions_from_file
的行读也只借用了它们)。
因此,最简单的正确修复方法是将 Question
更改为 拥有 其数据:
struct Question {
question: String,
answer: String
}
这样问题本身就可以保持其数据存在,问题就会消失。
我认为我们可以进一步改进:
首先,我们可以使用 String::lines
去除换行符周围的整个混乱,它将处理跨平台换行符,并将去除它们.
get_question_list
仅按值获取向量以附加到它并立即 return 它似乎也很奇怪。更直观的界面是:
- 采用
&mut
的“输出向量”,以便调用者可以预先确定大小或在多个负载中重用它,这在这种情况下似乎没什么用
- 或者在内部创建输出向量,这似乎是这里最明智的情况
这是我认为更令人满意的版本: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
.
在这一点上,因为我们不再关心 lines
是 Vec
,我们可以对 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
产生&str
,io::Lines
产生Result<String, io::Error>
:尝试读取时延迟报告IO错误,这意味着如果 read_to_string
失败,则每行读取都可能报告失败。
OTOH 因为 io::Lines
return 是 Result<String, ...>
我们可以直接使用 q
和 a
而无需将它们转换为 String
。
我的程序的目的是从文件中读取 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
是从文件中读取的,因此必然是函数的局部(除非你移动main
和 read_questions_from_file
的行读也只借用了它们)。
因此,最简单的正确修复方法是将 Question
更改为 拥有 其数据:
struct Question {
question: String,
answer: String
}
这样问题本身就可以保持其数据存在,问题就会消失。
我认为我们可以进一步改进:
首先,我们可以使用 String::lines
去除换行符周围的整个混乱,它将处理跨平台换行符,并将去除它们.
get_question_list
仅按值获取向量以附加到它并立即 return 它似乎也很奇怪。更直观的界面是:
- 采用
&mut
的“输出向量”,以便调用者可以预先确定大小或在多个负载中重用它,这在这种情况下似乎没什么用 - 或者在内部创建输出向量,这似乎是这里最明智的情况
这是我认为更令人满意的版本: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
.
在这一点上,因为我们不再关心 lines
是 Vec
,我们可以对 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
产生&str
,io::Lines
产生Result<String, io::Error>
:尝试读取时延迟报告IO错误,这意味着如果 read_to_string
失败,则每行读取都可能报告失败。
OTOH 因为 io::Lines
return 是 Result<String, ...>
我们可以直接使用 q
和 a
而无需将它们转换为 String
。