rust 允许模式解构,但不允许建构

qmb5sa22  于 2022-11-12  发布在  其他
关注(0)|答案(1)|浏览(182)

假设我有下面的src/lib.rs

pub enum Toppings {
    Almond,
    Caramel,
    Cookie,
    Marshmallow,
    ChocolateSprinkles,
}
pub enum Icecream {
    Chocolate(Toppings),
    Mint(Toppings),
    Vanilla(Toppings),
}

我想允许模式解构s.t.人们可以像使用任何其他枚举一样使用库与关联数据:

use Icecream::*;
use Toppings::*;

fn order_how_many(icecream: Icecream) -> usize {
    match icecream {
        Chocolate(Marshmallow) => 42,
        Vanilla(Cookie) => 69,
        _ => 42069,
    }
}

但同时我想禁止创建枚举及其相关数据的某些组合,例如,也许当地的冰淇淋店认为双巧克力太多,永远不会出售Icecream::Chocolate(Toppings::ChocolateSprinkles),所以我们应该完全禁止任何可能构建这种组合的可能性。我发现这在Rust中很难实现,因为我需要pub enum来允许模式销毁,但是公开enum意味着它的所有变体都是pub
我尝试了类似于有时可以在sealed trait pattern中找到的私有令牌,使用私有模块并通过#[forbid(missing_docs)],s.t.防止任何意外的pub use,只有crate实现可以决定什么Icecream/Toppings组合是可能的,但这使得模式匹配丑陋(在每个模式破坏中需要_..)。

mod icecream {
    // Intentionally NOT use /// comment for this mod
    // to prevent accidental `pub use`
    #[forbid(missing_docs)]
    mod hide {
        pub struct Hide;
    }
    pub enum Toppings {
        Almond,
        Caramel,
        Cookie,
        Marshmallow,
        ChocolateSprinkles,
    }
    pub enum Icecream {
        Chocolate(Toppings, hide::Hide),
        Mint(Toppings, hide::Hide),
        Vanilla(Toppings, hide::Hide),
    }
    pub use Icecream::*;
    pub use Toppings::*;
}

pub use icecream::*;
fn order_how_many(icecream: Icecream) -> usize { // OK
    match icecream {
        Chocolate(Marshmallow, _) => 42,
        Vanilla(Cookie, ..) => 69,
        _ => 42069,
    }
}
fn str_to_icecream(base: &str, topping: &str) -> Option<Icecream> { // Compile fail as intended
    if base.to_lowercase() == "chocollate" && topping.to_lowercase() == "chocolatesprinkles" {
        Some(Chocolate(ChocolateSprinkles, icecream::hide::Hide))
    } else {
        None
    }
}

在发布这个问题之前,SO建议我使用this,但它似乎也没有解决enum的问题,因为enumstruct不同,它不能在类型本身及其关联成员之间具有不同的可见性,这将使具有不同形状的关联数据的实现更加麻烦,例如,如果元组的长度不同,我可能必须实现一个trait并返回trait对象,如果我使用这个方法的话。
有没有一种更优雅/自然/简单的方法来做到这一点?或者一开始就应该完全避免这样的代码,也许因为它被认为是一种糟糕的做法?

t98cgbkg

t98cgbkg1#

如果不允许对enum进行构造,就不能允许对它们进行模式匹配。
但是,struct字段可以是私有的,这样就可以执行以下操作:

pub enum Toppings {
    Almond,
    Caramel,
    Cookie,
    Marshmallow,
    ChocolateSprinkles,
}

pub enum Flavour {
    Chocolate,
    Mint,
    Vanilla,
}

pub struct Icecream {
    pub flavour: Flavour,
    pub toppings: Toppings,
    _secret: (), // non-pub, prevents construction from outside the module
}

除了从同一个模块中的函数构造Icecream外,您不能再构造Icecream;这些是唯一允许访问_secret字段的内容。模块外部的代码仍然可以破坏这些值,但必须使用..以避免命名_secret字段。
您可以将其他函数重写为:

// this can be in any module
fn order_how_many(icecream: Icecream) -> usize { // OK
    match icecream {
        Icecream { flavour: Chocolate, .. } => 42,
        Icecream { flavour: Vanilla, toppings: Cookie, .. } => 69,
        _ => 42069,
    }
}

// this must be in the same module as the `Icecream` struct
fn str_to_icecream(base: &str, topping: &str) -> Option<Icecream> {
    if base.to_lowercase() == "chocolate" && topping.to_lowercase() == "chocolatesprinkles" {
        Some(
            Icecream { 
                flavour: Chocolate,
                toppings: ChocolateSprinkles,
                _secret: () 
            }
        )
    } else {
        None
    }
}

相关问题