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

Mic*_*ael 6 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),
    }
}

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可能恐慌的事实.


She*_*ter 4

不,这通常是不可能的,因为每个元素的大小可能会随着映射的执行而改变 ( 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 的回答中看到一个例子。