当前位置: 首页 > 知识库问答 >
问题:

是否有可能在不分配新Vec的情况下将一个功能映射到一个Vec上?

章承基
2023-03-14

我有以下内容:

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),
    ];
}

我现在想在data的每个元素上调用变换,并将结果值存储回data中。我可以做类似data.into_iter(). map(变换). Collection()的事情,但这将分配一个新的Vec。有没有一种方法可以就地执行此操作,重用data的分配内存?Rust中曾经有Vec::map_in_place,但它已经被删除了一段时间。

作为解决方法,我向 SomeType 添加了一个虚拟变体,然后执行以下操作:

for x in &mut data {
    let original = ::std::mem::replace(x, SomeType::Dummy);
    *x = transform(original);
}

这感觉不对,我必须处理代码中其他任何地方的<code>SomeType::Dummy</code>问题,尽管它不应该在这个循环之外可见。有没有更好的方法?

共有3个答案

缪修德
2023-03-14

您可以根据< code>take_mut或< code>replace_with板条箱编写< code>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替换为

冯驰
2023-03-14

不,通常不可能,因为每个元素的大小可能会随着映射的执行而改变(fn-transform(u8)-

即使大小相同,这也不是微不足道的。

在这种情况下,您不需要创建虚拟变体,因为创建空字符串很便宜;只有 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),
        };
    }
}
for x in &mut data {
    x.transform();
}

一个替代实现,只是替换字符串

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)
            }
        };
    }
}

一般来说,是的,你必须创建一些虚拟值,用安全的代码来做这些。很多时候,可以将整个元素包装在< code>Option中,调用< code>Option::take来达到同样的效果。

另请参阅:

  • 将字段移动到新变量时更改枚举变量

请参阅这个提议的现已关闭的RFC,了解大量相关讨论。我对RFC(及其背后的复杂性)的理解是,在一段时间内,您的值将具有未定义的值,这是不安全的。如果在那一秒发生了紧急情况,那么当您的值被丢弃时,您可能会触发未定义的行为,这是一件坏事。

如果您的代码在注释行处出错,那么< code>self的值是一个具体的已知值。如果它是某个未知值,删除该字符串将尝试删除该未知值,我们又回到了c中。这就是< code>Dummy值的目的——总是存储一个已知良好的值。

你甚至暗示了这一点(我的重点):

我必须处理SomeType::D ummy在代码中的其他地方,尽管它不应该在这个循环之外可见。

“应该”是问题所在。在恐慌期间,这个虚拟值是可见的。

另请参阅:

    < li >如何为结构的可变引用中的字段换入新值? < li >暂时移出借用的内容 < li >如何移出作为选项的结构字段?

现在删除的 Vec::html" target="_blank">map_in_place 实现跨越了近 175 行代码,其中大部分必须处理不安全的代码,并推理为什么它实际上是安全的!一些板条箱重新实施了这一概念,并试图使其安全;你可以在Sebastian Redl的答案中看到一个例子。

黄丰
2023-03-14

第一个问题不是映射,而是变换

transform拥有其参数的所有权,而Vec拥有其参数的所有权。任何一个都必须付出,在Vec上戳一个洞将是一个坏主意:如果转型恐慌怎么办?

因此,最好的解决方法是将< code>transform的签名改为:

fn transform(x: &mut SomeType) { ... }

然后你可以做:

for x in &mut data { transform(x) }

其他解决方案将是笨拙的,因为它们需要处理变换可能会恐慌的事实。

 类似资料:
  • 我想使用并使其直接进入给定的url,而不是从ribbon配置中获取主机。 我知道在Spring,cloud-feign默认与ribbon和eureka一起出现。 根据这个:https://cloud.spring.io/spring-cloud-netflix/multi/multi_spring-cloud-ribbon.html#spring-cloud-ribbon-without-eure

  • 问题内容: 这是我的代码: 我对Go很陌生,但据我所知,由于我将映射值传递给而不是指向映射的指针,因此该函数应修改映射的其他变量,因此不会影响变量的值。我希望它能打印出来。我测试了另一种情况: 在这种情况下,代码的行为符合我的预期。函数中的变量不受变量中更改的影响。那么为什么地图不同? 问题答案: 正确的做法是,当您将某些内容传递给函数时,将创建一个副本。但是,映射是对基础数据结构的某种描述符。因

  • 代码: https://play.rust-lang.org/?version=stable 返回此错误: 错误[E0277]:类型为

  • 我对生锈编程是新手。我想用递归实现合并排序。这是我的代码: 问题是,当我试图编译它时,我得到了以下错误: 你知道我为什么会犯这个奇怪的错误吗!看来,我错过了什么。

  • vec

    描述 (Description) 此函数使用指定EXPR的字符串作为无符号整数的向量。 NUMBITS参数是为位向量中的每个条目保留的位数。 这必须是从1到32的2的幂。请注意,偏移量是向量结束的标记,它会计算指定的位数以查找开始。 可以使用逻辑按位运算符|,&和^来操纵向量。 语法 (Syntax) 以下是此函数的简单语法 - vec EXPR, OFFSET, BITS 返回值 (Retur