Enum, private constructors, public matching

Suppose we had the following:

enum Foo {
  Any(i64),
  Prime(u64),
}

then we want to keep Foo::Prime private to ensure that we only pas it prime u64's.

However, we also want Foo::Any, Foo::Prime to be available for pattern matching even OUTSIDE the defining crate.

Is this possible?

I simultaneously want the choices of an enum to be (1) available for matching and (2) NOT available for constructors, when outside of the defining crate.

I don't think enums are designed to work that way.

I guess you could kinda fudge it using #[doc(hidden)] and a type which can't be constructed by the user.

mod private {
  pub struct Sealed;
}

pub enum Foo {
  Prime(u64),
  Any {
    value: i64,
    #[doc(hidden)]
    __super_secret_dont_construct_this_variant: private::Sealed,
  },
}
1 Like

I think you meant to attach the compiler directives to Prime rather than Any -- but otherwise, thanks for confirming that this is not Enum's intended purpose.

1 Like
pub struct Prime(u64);

pub enum Foo {
  Prime(Prime),
  Any(i64),
}

Would this work?

3 Likes

You could make the enum private, wrap it in the private field of a public newtype, then instead of insisting on the user matching, implement something like a visitor pattern where you match on the enum internally, and the user can provide the appropriate callback as a closure for each case (or in the form of an all-in-one Visitor object).

1 Like

I call this the "View" pattern (a particular instance of visitor pattern):

pub
enum FooView {
    Prime(u64),
    Any(i64),
}

pub
struct Foo /* = */ (
    FooView,
);

impl Foo {
    pub
    fn new (n: i64) -> Self { ... }
 
    /// Or `impl AsRef<FooView> for Foo {}`
    pub
    fn view (self: &'_ Self) -> &'_ FooView
    {
        &self.0
    }
}

fn main ()
{
    let foo = Foo::new(91);
    match foo.view() {
        | &FooView::Any(i) => { ... },
        | &FooView::Prime(u) => { unreachable!("{} == 91 is not prime!", u); },
    }
}

By never constructing a Foo from the public FooView, and not having a DerefMut or equivalent either, you can keep the prime invariant.

Although to keep such a "simple" invariant, I think I like @KrishnaSannasi's solution better (better encapsulation of the is_prime invariant).

1 Like