我有以下内容:
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),
}
}
let mut data = vec![SomeType::VariantA("hello".to_string()),
SomeType::VariantA("bye".to_string()),
SomeType::VariantB("asdf".to_string(), 34)];
我现在想在transform
的每个元素上调用data
,并将结果值存回data
。现在,我当然可以执行data.into_iter().map(transform).collect()
之类的操作,但这会分配一个新的Vec
。有没有办法在就地执行此操作,重用data
分配的内存?在Rust中曾经有过Vec::map_in_place
但它已经被删除了一段时间(我认为在1.4左右)。
作为解决方法,我目前向Dummy
添加SomeType
- 变体,然后执行以下操作:
for x in &mut data {
let original = ::std::mem::replace(x, SomeType::Dummy);
*x = transform(original);
}
但这感觉不对,而且,我必须在代码中的其他地方处理SomeType::Dummy
,尽管它应该永远不会在此循环之外可见。有没有更好的方法呢?
答案 0 :(得分:9)
您的第一个问题不是map
,而是transform
。
transform
获得其参数的所有权,而Vec
拥有其参数的所有权。任何一个人都必须给出,并在Vec
中挖出一个洞是个坏主意:如果transform
恐慌会怎么样?
因此,最好的解决方法是将transform
的签名更改为:
fn transform(x: &mut SomeType) { ... }
那么你可以这样做:
for x in &mut data { transform(x) }
其他解决方案将很笨重,因为他们需要处理transform
可能会恐慌的事实。
答案 1 :(得分:4)
不,通常不可能,因为每个元素的大小可能会随着映射的执行而变化(fn transform(u8) -> u32
)。即使尺寸相同,它也是非平凡的。 now removed implementation of Vec::map_in_place
跨越了近175行代码,大多数代码必须处理不安全的代码并推理它为什么实际上是安全的!
在本案中,您不需要创建Dummy
变体,因为创建空String
很便宜 - 只有3个指针大小的值而且没有堆分配:
impl SomeType {
fn transform(&mut self) {
use SomeType::*;
let old = mem::replace(self, VariantA(String::new()));
// note here...
*self = match old {
VariantA(s) => VariantB(s, 0),
VariantB(s, i) => VariantB(s, 2 * i),
};
}
}
for x in &mut data {
x.transform();
}
但总的来说,是的,你必须创建一些虚拟值来一般性地使用安全代码。很多时候,您可以将整个元素包装在Option
中以达到相同的效果。有关许多相关讨论,请参阅此proposed and now-closed RFC。 我对RFC(及其背后的复杂性)的理解是,在某个时间段内,您的值将具有未定义的值,即不安全。如果恐慌发生在那个确切的秒,那么当你的价值被取消时,你可能会触发未定义的行为,这是一件坏事。
如果您的代码在评论行处发生恐慌,则self
的值是具体的已知值。如果它是某个未知值,则删除该字符串将尝试删除该未知值,并且我们返回到C.这是Dummy
值的目的 - 始终存储已知良好值。
替代实施:
impl SomeType {
fn transform(&mut self) {
use SomeType::*;
let next = match *self {
VariantA(ref mut s) => {
let s = mem::replace(s, String::new());
VariantB(s, 0)
},
VariantB(ref mut s, i) => {
let s = mem::replace(s, String::new());
VariantB(s, 2 * i)
},
};
*self = next;
}
}
答案 2 :(得分:1)
您可以根据take_mut
或replace_with
条板箱来写map_in_place
:
fn map_in_place<T, F>(v: &mut [T], f: F)
where
F: Fn(T) -> T,
{
for e in v {
take_mut::take(e, f);
}
}
但是,如果在提供的功能中出现此紧急情况,程序将完全中止;您无法从恐慌中恢复过来。
或者,您可以提供一个占位符元素,该元素位于内部函数执行时的空白处:
use std::mem;
fn map_in_place_with_placeholder<T, F>(v: &mut [T], f: F, mut placeholder: T)
where
F: Fn(T) -> T,
{
for e in v {
let mut tmp = mem::replace(e, placeholder);
tmp = f(tmp);
placeholder = mem::replace(e, tmp);
}
}
如果出现这种情况,您提供的占位符将位于紧急情况的插槽中。
最后,您可以按需生成占位符;在第一版中,基本上用take_mut::take
替换了take_mut::take_or_recover
。