我想从标准输入读取一行,然后用空格分割它并处理这些部分。
一个简单的 read_line 就可以工作,因为它返回一个拥有的字符串:
fn read_line() -> String {
let mut str: String = String::new();
stdin().read_line(&mut str).unwrap();
return str;
}
但是当我想使用 String 并返回一个拥有的 Split 时,我无法做到这一点,其生命周期超出了创建它的函数的范围。
创建
Split
的自有版本比看起来更棘手。但如果你尝试显而易见的事情:
// doesn't compile
fn owned_split(s: String) -> impl Iterator<Item = String> {
s.split_whitespace().map(|sub| sub.to_string())
}
这不会编译,因为
SplitWhitespace
返回的 s.split_whitespace()
想要保存对字符串的引用。这个 owned_split()
有效地返回了对本地的引用,它不会编译(也不应该编译)。理想情况下,我们会返回一个包含原始字符串和指向它的 SplitWhitespace
的结构,但这不起作用,因为借用检查器尚不支持自引用结构。可以使用 one of the 自引用板条箱使其在“安全”代码中工作,但让我们首先探索其他选项。
解决该问题的一个简单方法是在每个
split_whitespace()
上重新调用 next()
,并每次都返回“第一个”项目。这需要一些技巧来弄清楚在哪里继续调用它,但这是可以做到的:
fn owned_split(s: String) -> impl Iterator<Item = String> {
let mut pos = 0;
std::iter::from_fn(move || {
let sub = s[pos..].split_whitespace().next()?;
// next search position is at the end of `sub`
pos = sub.as_bytes().as_ptr_range().end as usize - s.as_ptr() as usize;
Some(sub.to_owned())
})
}
最后,这是一个使用 ouroboros 创建自引用结构的版本:
use std::str::SplitWhitespace;
use ouroboros::self_referencing;
fn owned_split(s: String) -> impl Iterator<Item = String> {
#[self_referencing]
struct OwnedSplit {
owner: String,
#[borrows(owner)]
#[not_covariant]
split: SplitWhitespace<'this>,
}
impl Iterator for OwnedSplit {
type Item = String;
fn next(&mut self) -> Option<String> {
self.with_split_mut(|split| split.next().map(|s| s.to_owned()))
}
}
OwnedSplit::new(s, |s| s.split_whitespace())
}