是否可以在Vec上映射函数而不分配新的Vec?

时间:2017-01-24 15:30:39

标签: rust

我有以下内容:

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,尽管它应该永远不会在此循环之外可见。有没有更好的方法呢?

3 个答案:

答案 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_mutreplace_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