我有以下内容:
enum SomeType {
VariantA(String),
VariantB(String, i32),
}
fn transform(x: SomeType) -> SomeType {
// very complicated transformation, reusing parts of x in order to produce result:
match x {
SomeType::VariantA(s) => SomeType::VariantB(s, 0),
SomeType::VariantB(s, i) => SomeType::VariantB(s, 2 * i),
}
}
fn main() {
let mut data = vec![
SomeType::VariantA("hello".to_string()),
SomeType::VariantA("bye".to_string()),
SomeType::VariantB("asdf".to_string(), 34),
];
}
Run Code Online (Sandbox Code Playgroud)
我现在想调用transform每个元素data并将结果值存回data.现在,我当然可以做类似的事情,data.into_iter().map(transform).collect()但这将分配一个新的Vec.有没有办法在就地执行此操作,重用已分配的内存data?有一次Vec::map_in_place在Rust,但它已被删除了一段时间(我认为在1.4左右).
作为解决方法,我目前添加Dummy-variant SomeType,然后执行以下操作:
for x in &mut data {
let original = ::std::mem::replace(x, SomeType::Dummy);
*x = transform(original);
}
Run Code Online (Sandbox Code Playgroud)
但这感觉不对,而且,我必须处理SomeType::Dummy代码中的其他地方,尽管它应该永远不会在这个循环之外可见.有没有更好的方法呢?
Mat*_* M. 10
你的第一个问题不是map,它是transform.
transform取得其论点的所有权,同时Vec拥有其论据.任何一个人都必须给予,并且在这个问题上挖一个洞Vec会是一个坏主意:如果transform恐慌怎么办?
因此,最好的解决方法是将签名更改transform为:
fn transform(x: &mut SomeType) { ... }
Run Code Online (Sandbox Code Playgroud)
那么你可以这样做:
for x in &mut data { transform(x) }
Run Code Online (Sandbox Code Playgroud)
其他解决方案将是笨重的,因为他们将需要处理transform可能恐慌的事实.
不,这通常是不可能的,因为每个元素的大小可能会随着映射的执行而改变 ( fn transform(u8) -> u32)。
即使尺寸相同,这也不是微不足道的。
在这种情况下,您不需要创建一个Dummy变体,因为创建一个空的变体String很便宜;只有 3 个指针大小的值并且没有堆分配:
impl SomeType {
fn transform(&mut self) {
use SomeType::*;
let old = std::mem::replace(self, VariantA(String::new()));
// Note this line for the detailed explanation
*self = match old {
VariantA(s) => VariantB(s, 0),
VariantB(s, i) => VariantB(s, 2 * i),
};
}
}
Run Code Online (Sandbox Code Playgroud)
for x in &mut data {
x.transform();
}
Run Code Online (Sandbox Code Playgroud)
替代实现只是替换String:
impl SomeType {
fn transform(&mut self) {
use SomeType::*;
*self = match self {
VariantA(s) => {
let s = std::mem::replace(s, String::new());
VariantB(s, 0)
}
VariantB(s, i) => {
let s = std::mem::replace(s, String::new());
VariantB(s, 2 * *i)
}
};
}
}
Run Code Online (Sandbox Code Playgroud)
一般来说,是的,您必须创建一些虚拟值才能使用安全代码来执行此操作。很多时候,您可以将整个元素包裹起来Option并调用Option::take以达到相同的效果。
也可以看看:
请参阅此提议且现已关闭的 RFC以获取大量相关讨论。我对该 RFC(及其背后的复杂性)的理解是,在一段时间内您的值将具有未定义的值,这是不安全的。如果在那一刻发生恐慌,那么当你的值下降时,你可能会触发未定义的行为,这是一件坏事。
如果您的代码在注释行处出现恐慌,则 的值self是一个具体的已知值。如果它是某个未知值,则删除该字符串将尝试删除该未知值,然后我们又回到了 C。这就是该值的目的Dummy- 始终存储一个已知良好的值。
你甚至暗示了这一点(强调我的):
我必须处理
SomeType::Dummy代码中的其他地方,尽管它在这个循环之外永远不应该可见
这个“应该”就是问题所在。在恐慌期间,该虚拟值是可见的。
也可以看看:
现在删除的实现Vec::map_in_place跨越了近 175 行代码,其中大部分必须处理不安全代码并推理为什么它实际上是安全的!一些 crates 重新实现了这个概念并试图使其安全;你可以在Sebastian Redl 的回答中看到一个例子。
| 归档时间: |
|
| 查看次数: |
510 次 |
| 最近记录: |