如何在 Rust 中匹配的 experssion 上保留匹配臂中的枚举变体

How to preserve enum variant in match arm on matched experssion in Rust

提问人:weilbith 提问时间:9/18/2023 更新时间:9/18/2023 访问量:64

问:

我认为最好用一个例子来描述这个问题:

enum SensitiveVector<T> {
  Empty,
  Filled(Vec<T>),
}

impl<T> for SensitiveVector<T> {
  pub fn append(mut self, other: Self) -> Self {
    match (&self, &other) {
      (Empty, Empty) => Empty,
      (Empty, Filled(_)) => other,
      (Filled(_), Empty) => self,
      (mut vector @ Filled(_), Filled(other_items) => {
        vector.0.append(other_items);
        vector
      }
    }
  }
}

我正在努力提高效率,而不是简单地克隆向量或类似的东西,而是(希望)有效地重用内存。 无论如何,匹配表达式的第四臂是我的问题。我根本不知道如何将实例的向量项附加到此臂是否匹配。我尝试应用我在匹配臂中看到的任何语法,但它总是告诉我类型只是,因此无法访问元组字段。otherself&SensitiveVector<T>0

我知道代码可能很糟糕。但是谁能告诉我如何做这样的事情?当然,我可以只使用这两个向量并创建一个新实例,但我认为扩展更有效。SensitiveVectorself

感谢您的任何帮助并解释我缺少的语法/概念/规则。

火匹配

评论

0赞 FZs 9/18/2023
实际上,创建/克隆等空向量已经非常便宜了:空向量不会分配,它只是一个长度和容量设置为零的悬空指针。

答:

3赞 Masklinn 9/18/2023 #1

问题有两个方面:

  1. 您匹配的是 ,因此模式中的值始终是共享引用,它们不能被拥有(返回),也不能被修改。(&self, &other)
  2. 枚举变体不是 rust 中的类型,并且 rust 没有类型细化。所以会给你一个(充其量,这里是一个),你不能从枚举本身访问枚举变体的字段,所以如果你的目标是更新内容,模式匹配在很大程度上是浪费的。vector @ Filled(_)vector: SensitiveVector&SensitiveVector

由于您无论如何都要按值获取,因此第一个修复方法是也匹配它们,这意味着您还需要返回在第二和第三臂中匹配的值,或者需要从内容中重建值:selfother

enum SensitiveVector<T> {
  Empty,
  Filled(Vec<T>),
}
use SensitiveVector::*;

impl<T> SensitiveVector<T> {
  pub fn append(mut self, other: Self) -> Self {
    match (self, other) {
      (Empty, Empty) => Empty,
      (Empty, v @ Filled(_)) => v,
      (v @ Filled(_), Empty) => v,
      (mut vector @ Filled(_), Filled(other_items)) => {
        vector.0.append(other_items);
        vector
      }
    }
  }
}

这并不能修复第 4 个臂,但现在您应该将内部向量移出,合并向量,然后重新包装结果,而不是使用 @-pattern:

enum SensitiveVector<T> {
  Empty,
  Filled(Vec<T>),
}
use SensitiveVector::*;

impl<T> SensitiveVector<T> {
  pub fn append(self, other: Self) -> Self {
    match (self, other) {
      (Empty, Empty) => Empty,
      (Empty, v @ Filled(_)) => v,
      (v @ Filled(_), Empty) => v,
      (Filled(mut v), Filled(ref mut other)) => {
        v.append(other);
        Filled(v)
      }
    }
  }
}

评论

0赞 weilbith 9/18/2023
感谢您的帮助!我已经用第 2 只和第 3 只手臂在赛道上,但第 4 只手臂没有做同样的事情。因此,如果是最后一个手臂,我必须始终构建一个新实例?假设变体是多元组或结构体,我是否总是需要为此实例分配新内存并引用前一个实例的数据?希望这是有道理的......编辑:Filled
0赞 weilbith 9/18/2023
因为我仍在学习记忆中实际发生的事情。所以我知道这很挑剔。但是在第一臂中,只返回两个匹配值中的一个而不是创建一个新实例会更“便宜”吗?我想如果是空的枚举变体,这并不重要。但是对于其他情况呢?
1赞 cafce25 9/18/2023
不,一点也不,生成的二进制文件将完全相同@weilbith。
1赞 cafce25 9/18/2023
您还可以使用 或 模式将事例 2 和 3 联接到(Empty, v@Filled(_)) | (v@Filled(_), Empty) => v
0赞 weilbith 9/18/2023
有趣。我会看看哪个代码读起来更好。但总的来说很酷!多谢。
5赞 prog-fh 9/18/2023 #2

值和引用之间存在一些不匹配。 由于您似乎想要处理值(使用参数来发出新值),因此我建议您一直使用值(尤其是删除 match 语句中的引用)。

一旦 match 语句解构 或 ,这两个值就不再存在,只有解构的成员仍然存在,然后你必须从这些成员重新创建一个枚举(而不是尝试重用或作为一个整体)。selfotherselfother

这是您的示例,并进行了一些更正。

#[derive(Debug)]
enum SensitiveVector<T> {
    Empty,
    Filled(Vec<T>),
}

impl<T> SensitiveVector<T> {
    fn append(
        self,
        other: Self,
    ) -> Self {
        match (self, other) {
            (Self::Empty, Self::Empty) => Self::Empty,
            (Self::Empty, Self::Filled(other_items)) => {
                Self::Filled(other_items)
            }
            (Self::Filled(items), Self::Empty) => Self::Filled(items),
            (Self::Filled(mut items), Self::Filled(mut other_items)) => {
                items.append(&mut other_items);
                Self::Filled(items)
            }
        }
    }
}

fn main() {
    {
        let sv1 = SensitiveVector::<i32>::Empty;
        let sv2 = SensitiveVector::<i32>::Empty;
        println!("empty and empty: {:?}", sv1.append(sv2));
    }
    {
        let sv1 = SensitiveVector::<i32>::Empty;
        let sv2 = SensitiveVector::Filled(vec![1, 2, 3]);
        println!("empty and filled: {:?}", sv1.append(sv2));
    }
    {
        let sv1 = SensitiveVector::Filled(vec![1, 2, 3]);
        let sv2 = SensitiveVector::<i32>::Empty;
        println!("filled and empty: {:?}", sv1.append(sv2));
    }
    {
        let sv1 = SensitiveVector::Filled(vec![1, 2, 3]);
        let sv2 = SensitiveVector::Filled(vec![4, 5, 6]);
        println!("filled and filled: {:?}", sv1.append(sv2));
    }
}
/*
empty and empty: Empty
empty and filled: Filled([1, 2, 3])
filled and empty: Filled([1, 2, 3])
filled and filled: Filled([1, 2, 3, 4, 5, 6])
*/

评论

0赞 weilbith 9/18/2023
非常感谢您的帮助。但是,通过始终构造新的枚举变体,这意味着我总是为此取消分配和分配一些内存,对吗?尽管里面的向量只会保留在堆上的位置。正确?
1赞 prog-fh 9/18/2023
不,我们没有分配/取消分配任何东西,因为我们重用了从中获取的向量来创建结果。就字节而言,它大致只是一个指针和两个整数(向量的内容不重复)。如果超出容量,我们将一个向量附加到另一个向量时,就会发生唯一的重新分配,但在处理向量时这是意料之中的。self
2赞 cafce25 9/18/2023
@weilbith给编译器更多的信任,它认为 FilledVec 是一样的,所以所有这些都被优化了。不要过早地优化你的东西。
0赞 weilbith 9/18/2023
好。谢谢!我期待着在枚举变体包含更多数据的一些“更大”的情况下检查这一点。但我想编译器还是很聪明的。