Why must I expose my enum just to implement std::ops::Index?

If I try to compile code including the following snippets I get the error

private type in exported type signature
impl<'a> Index<SomeEnum> for Vec<&'a str> {

enum SomeEnum {
Variant0 = 0,
Variant1,
Variant2,
.
.
.,
VariantN
}

impl<'a> Index for Vec<&'a str> {
type Output = str;

fn index(&self, idx: SomeEnum) -> &str {
    self[idx as usize]
}

}

If I make the enum pub the code compiles.

The enum is an implementation detail that shouldn't be exposed. I don't want to expose it.

Is there a way to handle this I've missed?

Wrap it in a newtype.

I suspect this is because both Index and Vec are public. That error exists to try and protect people from accidentally exposing uncallable interfaces (i.e. it's public, but requires something private to call).

Seems like this particular case could maybe be relaxed.

I've read as much as I could find about newtype (which isn't much). I couldn't find an example of my problem being solved by a newtype.

I don't get it. Would you elaborate a little?

You can create a newtype that has a single field that consists of your enum. The newtype will be public, but the field can be private. In this way, you will have a public type that can be referred to from outside your module, but the contents are still private. (playpen)

pub struct SomeEnumWrapper(SomeEnum);

impl<'a> Index<SomeEnumWrapper> for Vec<&'a str> {
    type Output = str;

    fn index(&self, idx: SomeEnumWrapper) -> &str {
        self[idx.0 as usize]
    }
}

Making the newtype public is what I was missing.

So, at some level, there has to be a public version of the type I'm trying to keep private.

Hmmmmm, at first glance I don't like that. It's exposing implementation details with no clear benefit.

Thanks for the explanation everyone.

1 Like

How is the existence of a data type an implementation detail? You don't need to make the field public, nor does it have to have any public methods. It can be as opaque as you want; it just has to have a name.

"How is the existence of a data type an implementation detail?"

The enum is used as indexes into a vector of strings that was created by splitting a string. Effectively I want to turn the string values in the vector into a higher level abstraction that is typed (not a struct of strings).

The entire point of the crate is that users of the crate should not have to worry about the original string of values. So exposing the enum (or a wrapper) is defeating the purpose of the crate. Also, that enum could change over time as new fields are added to the original strings. Users should only have to worry about the higher level struct not the array of string values or the indexes of those string values.

Therefore by making the enum or a wrapper of the enum public, it's exposing implementation details. That wrapper or the enum itself becomes part of the public API. And experience has shown that users will use anything that's public.

Also, now that I've done the implementation of a newtype, I also don't like the newtype pattern because it's an unnecessary level of indirection. Instead of using the enum I have to use the wrapper and, again, there's almost no value in the wrapper. So I've rejected the newtype pattern for this case.

SomeEnum is not required to be public, this was fixed recently.
The example compiles on nightly playpen.

Thanks for pointing that out. I just confirmed it does indeed compile on nightly without pub. That's great.

I don't even know what to search for to find the details of the change. Any chance you have a link to the change details?

https://github.com/rust-lang/rust/pull/29973

In particular, impl Index<SomeEnum> for Vec is considered a private impl, because it has private trait Index<SomeEnum>, and the trait Index<SomeEnum> is considered private because it has private component SomeEnum.

I get it now; the entire impl is an implementation detail, not just the variants of the enum.

Thank you.