数组/拆分条目的模式匹配

问题描述 投票:0回答:1

我正在 Rust 中实现一种 BSP 树,我想编写一个函数来告诉节点属于父节点的哪一侧。这就是我拥有的

enum Side {
    L = 0,
    R = 1,
}

enum RegionKind {
    Split { 
        subregion: [Option<Rc<Region>>; 2],
    },
    Client{ 
        window: i32,
    },
}

struct Region {
    kind: RegionKind,
    container: Option<Rc<Region>>,
    tags: u8,
}

impl Region {
    fn from(&self) -> Option<Side> {
        if let RegionKind::Split{subregion,..} = &self.container.as_ref()?.kind {
            match &subregion[0] {
                None => None,
                Some(r) => if eq(Rc::<Region>::as_ptr(&r),self) {
                    Some(Side::L)
                } else {
                    None
                }.or_else(|| { match &subregion[1] {
                    None => None,
                    Some(r) => if eq(Rc::<Region>::as_ptr(&r),self) {
                        Some(Side::R)
                    } else {
                        None
                    }
                }})
            }
        } else {
            None
        }
    }
}

目前有两件事使这个功能变得相当庞然大物。

  1. 我需要重复检查 subregion[0] 和 subregion[1]。我真的希望能够返回索引的

    Side
    (假设 L = 0 和 R = 1,如枚举声明中所示)并能够将它们组合成一个,因此为什么我要求匹配数组的元素。就像您如何使用
    Some(r) => ...
    r
    Some

    中拆箱一样
  2. 我需要检查

    RegionKind
    是否为
    Split
    。我可以断言任何区域的
    container
    始终将
    RegionKind::Split
    作为其
    kind
    ,因此我希望能够完全消除
    if let else
    (如果必须的话,我更愿意在 else 块中使用
    panic!()
    )也有它,但是编译器对返回类型感到不安,所以我无论如何都需要在末尾添加
    None
    ,这最终会被标记为无法访问)

我认为可能有一些神秘的 Rust 语法可以使这变得更加简洁,但我只是不知道或者不知道如何在这里使用它。同样的函数可以是 C 中的单行宏。

rust pattern-matching
1个回答
0
投票

我会将一些东西移至其他功能。

impl RegionKind {
    // you may want to make this a method on `Region` instead
    fn subregion(&self) -> Option<&[Option<Rc<Region>>; 2]> {
        if let RegionKind::Split { subregion, .. } = self {
            Some(subregion)
        } else {
            None
        }
    }
}

impl Region {
    fn eq_then_side(&self, r: &Region, side: Side) -> Option<Side> {
        eq(self, r).then_some(side)
    }
}

然后就可以写得简洁一点了。

pub fn from(&self) -> Option<Side> {
    let [sub_left, sub_right] = self.container.as_ref()?.kind.subregion().unwrap();

    sub_left
        .as_ref()?
        .eq_then_side(self, Side::L)
        .or_else(|| sub_right.as_ref()?.eq_then_side(self, Side::R))
}

请注意,如果您发现自己匹配

Option
,尤其是
None => None
,您几乎肯定可以使用
Option
方法
来代替,通常是
map
and_then
。在这种情况下,
?
将起作用,因为正在返回两个
None
值。

我相信您不需要使用

Rc::as_ptr
,因为您从不使用指针进行阅读,而且
Rc
也永远不会掉落。您可以直接从共享引用派生指针。

你所说的恐慌是不正确的。您始终可以用恐慌替换表达式,除非类型推断需要该返回,但由于您有显式返回类型,因此此处不适用。我在上面使用了

unwrap
作为恐慌。

如果你想让它真正成为一种表达方式,你可以这样做,但我不会推荐它,因为它变得太复杂了。

pub fn from(&self) -> Option<Side> {
    self.container
        .as_ref()?
        .kind
        .subregion()
        .unwrap()
        .iter()
        .map_while(|sub| sub.as_ref())
        .zip([Side::L, Side::R])
        .find_map(|(sub, side)| sub.eq_then_side(self, side))
}

您也可以将

[Side::L, Side::R]
替换为
(0u8..).map(|n| Side::try_from(n).unwrap())
并实现
TryFrom<u8> for Side
,但当您只有两个变体时我不会打扰。 Rust 没有为显式枚举判别式提供许多方便的用途。

© www.soinside.com 2019 - 2024. All rights reserved.