交替使用 str 和 String
Using str and String interchangably
假设我正在尝试使用 &str
在 Rust 中做一个奇特的零拷贝解析器,但有时我需要修改文本(例如实现变量替换)。我真的很想做这样的事情:
fn main() {
let mut v: Vec<&str> = "Hello there $world!".split_whitespace().collect();
for t in v.iter_mut() {
if (t.contains("$world")) {
*t = &t.replace("$world", "Earth");
}
}
println!("{:?}", &v);
}
当然,t.replace()
返回的 String
还不够长。有解决这个问题的好方法吗?也许有一种类型表示 "ideally a &str
but if necessary a String
"?或者也许有一种方法可以使用生命周期注释来告诉编译器返回的 String
应该保持活动状态直到 main()
结束(或者具有与 v
相同的生命周期)?
std::borrow::Cow
,具体用作Cow<'a, str>
,其中'a
是被解析字符串的生命周期。
use std::borrow::Cow;
fn main() {
let mut v: Vec<Cow<'static, str>> = vec![];
v.push("oh hai".into());
v.push(format!("there, {}.", "Mark").into());
println!("{:?}", v);
}
产生:
["oh hai", "there, Mark."]
Rust 以 Cow
(写入时克隆)类型的形式完全满足您的需求。
use std::borrow::Cow;
fn main() {
let mut v: Vec<_> = "Hello there $world!".split_whitespace()
.map(|s| Cow::Borrowed(s))
.collect();
for t in v.iter_mut() {
if t.contains("$world") {
*t.to_mut() = t.replace("$world", "Earth");
}
}
println!("{:?}", &v);
}
正如@sellibitze 正确指出的那样,to_mut()
创建了一个新的 String
,这导致堆分配存储以前借用的值。如果你确定你只有借用的字符串,那么你可以使用
*t = Cow::Owned(t.replace("$world", "Earth"));
如果 Vec 包含 Cow::Owned
个元素,这仍然会丢弃分配。您可以使用以下 非常脆弱和不安全的 代码来防止这种情况(它直接对 UTF-8 字符串进行基于字节的操作,并依赖于替换恰好是完全相同的数字这一事实字节数。)在你的 for 循环中。
let mut last_pos = 0; // so we don't start at the beginning every time
while let Some(pos) = t[last_pos..].find("$world") {
let p = pos + last_pos; // find always starts at last_pos
last_pos = pos + 5;
unsafe {
let s = t.to_mut().as_mut_vec(); // operating on Vec is easier
s.remove(p); // remove $ sign
for (c, sc) in "Earth".bytes().zip(&mut s[p..]) {
*sc = c;
}
}
}
请注意,这正是针对“$world”-> "Earth" 映射量身定制的。任何其他映射都需要在不安全代码中仔细考虑。
假设我正在尝试使用 &str
在 Rust 中做一个奇特的零拷贝解析器,但有时我需要修改文本(例如实现变量替换)。我真的很想做这样的事情:
fn main() {
let mut v: Vec<&str> = "Hello there $world!".split_whitespace().collect();
for t in v.iter_mut() {
if (t.contains("$world")) {
*t = &t.replace("$world", "Earth");
}
}
println!("{:?}", &v);
}
当然,t.replace()
返回的 String
还不够长。有解决这个问题的好方法吗?也许有一种类型表示 "ideally a &str
but if necessary a String
"?或者也许有一种方法可以使用生命周期注释来告诉编译器返回的 String
应该保持活动状态直到 main()
结束(或者具有与 v
相同的生命周期)?
std::borrow::Cow
,具体用作Cow<'a, str>
,其中'a
是被解析字符串的生命周期。
use std::borrow::Cow;
fn main() {
let mut v: Vec<Cow<'static, str>> = vec![];
v.push("oh hai".into());
v.push(format!("there, {}.", "Mark").into());
println!("{:?}", v);
}
产生:
["oh hai", "there, Mark."]
Rust 以 Cow
(写入时克隆)类型的形式完全满足您的需求。
use std::borrow::Cow;
fn main() {
let mut v: Vec<_> = "Hello there $world!".split_whitespace()
.map(|s| Cow::Borrowed(s))
.collect();
for t in v.iter_mut() {
if t.contains("$world") {
*t.to_mut() = t.replace("$world", "Earth");
}
}
println!("{:?}", &v);
}
正如@sellibitze 正确指出的那样,to_mut()
创建了一个新的 String
,这导致堆分配存储以前借用的值。如果你确定你只有借用的字符串,那么你可以使用
*t = Cow::Owned(t.replace("$world", "Earth"));
如果 Vec 包含 Cow::Owned
个元素,这仍然会丢弃分配。您可以使用以下 非常脆弱和不安全的 代码来防止这种情况(它直接对 UTF-8 字符串进行基于字节的操作,并依赖于替换恰好是完全相同的数字这一事实字节数。)在你的 for 循环中。
let mut last_pos = 0; // so we don't start at the beginning every time
while let Some(pos) = t[last_pos..].find("$world") {
let p = pos + last_pos; // find always starts at last_pos
last_pos = pos + 5;
unsafe {
let s = t.to_mut().as_mut_vec(); // operating on Vec is easier
s.remove(p); // remove $ sign
for (c, sc) in "Earth".bytes().zip(&mut s[p..]) {
*sc = c;
}
}
}
请注意,这正是针对“$world”-> "Earth" 映射量身定制的。任何其他映射都需要在不安全代码中仔细考虑。