我正在 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
}
}
}
目前有两件事使这个功能变得相当庞然大物。
我需要重复检查 subregion[0] 和 subregion[1]。我真的希望能够返回索引的
Side
(假设 L = 0 和 R = 1,如枚举声明中所示)并能够将它们组合成一个,因此为什么我要求匹配数组的元素。就像您如何使用 Some(r) => ...
将 r
从 Some
中拆箱一样
我需要检查
RegionKind
是否为Split
。我可以断言任何区域的 container
始终将 RegionKind::Split
作为其 kind
,因此我希望能够完全消除 if let else
(如果必须的话,我更愿意在 else 块中使用 panic!()
)也有它,但是编译器对返回类型感到不安,所以我无论如何都需要在末尾添加 None
,这最终会被标记为无法访问)
我认为可能有一些神秘的 Rust 语法可以使这变得更加简洁,但我只是不知道或者不知道如何在这里使用它。同样的函数可以是 C 中的单行宏。
我会将一些东西移至其他功能。
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 没有为显式枚举判别式提供许多方便的用途。