rust - 为什么使用`flat_map`时需要收集到向量中?
问题描述
我正在从事Project Euler 96以自学 Rust。我编写了这段代码来读取文件并将其转换为整数向量(Playground)。
let file = File::open(&args[1]).expect("Sudoku file not found");
let reader = BufReader::new(file);
let x = reader
.lines()
.map(|x| x.unwrap())
.filter(|x| !x.starts_with("Grid"))
.flat_map(|s| s.chars().collect::<Vec<_>>()) // <-- collect here!
.map(|x| x.to_digit(10).unwrap())
.collect::<Vec<_>>();
这一切都很好,但我很困惑为什么我必须收集到我的向量中flat_map
(我假设创建将立即销毁的不需要的向量是低效的)。如果我不收集,它不会编译:
error[E0515]: cannot return value referencing function parameter `s`
--> src/main.rs:13:23
|
13 | .flat_map(|s| s.chars())
| -^^^^^^^^
| |
| returns a value referencing data owned by the current function
| `s` is borrowed here
文档中的示例显示了几乎相同的代码,但不需要收集:
let words = ["alpha", "beta", "gamma"];
// chars() returns an iterator
let merged: String = words.iter()
.flat_map(|s| s.chars())
.collect();
assert_eq!(merged, "alphabetagamma");
那么为什么我的代码不同呢?
解决方案
迭代器reader.lines().map(|x| x.unwrap())
迭代String
项目,即按值。因此,在 中.flat_map(|s| ...)
,变量s
具有类型String
(即拥有,未借用)。换句话说:字符串现在是一个局部变量并且存在于函数中。这是一个简单的规则,您不能返回对局部变量的引用(请参阅此 Q&A)。但这正是s.chars()
它的作用,即使它有点隐藏。
看看:_str::chars
pub fn chars(&self) -> Chars<'_>
可以看到字符串是借来的。返回的Chars
对象包含对原始字符串的引用。这就是为什么我们不能s.chars()
从关闭中返回。
那么为什么我的代码不同呢?
在文档的示例中,迭代器words.iter()
实际上迭代了 type 的项目&&'static str
。调用s.chars()
也将返回一个Chars
借用字符串的对象,但该字符串的生命周期是(永远存在),因此从该闭包'static
返回没有问题。Chars
解决方案?
如果标准库有一个OwnedChars
迭代器,它会消耗 a String
,并且在迭代器被删除后像Chars
和删除字符串一样工作,那就太好了。在这种情况下,可以调用s.owned_chars()
,因为返回的对象没有引用 local s
,而是拥有它。但是:标准库中不存在这样的自有迭代器!
我假设创建将立即销毁的不需要的向量是低效的
是的,在某种程度上确实如此。但是您可能已经错过了reader.lines()
迭代器也创建了类型的临时对象String
。那些也或多或少地立即被摧毁!所以即使没有collect
in ,flat_map
你也有一堆不必要的分配。请注意,有时这没关系。在这种情况下,我猜与您必须实现的实际算法相比,输入解析非常快。所以……只是collect
?在这种情况下可能没问题。
如果您想进行高性能输入解析,我认为您将无法避免标准循环,特别是为了避免不必要的String
分配。(游乐场)
let mut line = String::new();
let mut input = Vec::new();
loop {
line.clear(); // clear contents, but keep memory buffer
// TODO: handle IO error properly
let bytes_read = reader.read_line(&mut line).expect("IO error");
if bytes_read == 0 {
break;
}
if line.starts_with("Grid") {
continue;
}
// TODO: handle invalid input error
input.extend(line.trim().chars().map(|c| c.to_digit(10).unwrap()));
}
推荐阅读
- javascript - 使用 PHP 脚本显示过滤输入字段值的 MYSQL 数据
- javascript - Material UI 内联样式 - 特定组件颜色
- c - 我在使用 fgets 时缺少什么?
- jboss - Keycloak,以编程方式创建客户端 - 403
- html - Chrome 不会播放 Icecast 流媒体链接
- excel - 使用另一个单元格值来引用范围
- c++ - 是否可以在编译时限制类的实例数?
- dart - pub global activate 命令 - $HOME/.pub-cache/bin 不在路径上
- c++ - 从 C++ 中的原始字节解码协议缓冲区
- java - 带有 jdk 1.8 的 Android 的 Switch(String)