Why can't I return a private enum from a public function?


#1

Playpen link: https://play.rust-lang.org/?gist=feb8e3543fb145633fa7&version=stable

I keep getting tripped up by this: if I implement something in a module with an enum, with methods etc., there doesn’t seem to be a way to allow consumers of the module to use the impls defined for the enum without just making the whole thing public. But I may wish to preserve invariants by only allowing values to be created or manipulated by my own functions, and there doesn’t seem to be a way to do that. (I’m thinking of an analogy to exporting the type but not the data constructors in Haskell.)

There does seem to be a workaround, defining a public newtype struct with a private member containing the enum. But that seems kind of a pain, especially if you want to define the impls on both.


#2

If you are returning a value from a public function, dosen’t that allow consumers to manipulate that value?


#3

well … why would it? I mean, I understand that this works: https://play.rust-lang.org/?gist=1f3b7ce8b497a80b7c1d&version=stable and lets you manipulate the internals of an enum’s variants. But it only works because the variants This and That are exported. If those were private, while still allowing the type to be returned, it wouldn’t.


#4

I’m confused, what do you mean by “manipulate a value”.


#5

Suppose I have a haskell module like this:

module Foo (Thing, getThing, flipThing) where
data Thing = This | That deriving Show
getThing = This

flipThing This = That
flipThing That = This

This exports the type Thing, which implements the Show typeclass (like the Debug trait), and the functions getThing and flipThing.

In another module I can do this:

import Foo as F
t = F.getThing             -- t is This
t' = F.flipThing t         -- t' is That
s = (show t) ++ (show t')  -- "ThisThat"

But I can’t create a value of type Thing myself, I can only use getThing, flipThing, and the methods of the typeclass. I can’t pattern-match either (since the data constructors aren’t exported).

You can’t do this in Rust with an enum. Instead, you have to create an enum and a struct.


#6

For that to work you also would have to obscure the documentation of your API and close the source, to make it difficult for the user to figure out that getThing produces a This value and flipThing then produces a That. If you give the user that knowledge, then you would only be preventing him from matching.

The wrapper seems like the proper solution to hide the real type, if you put a type’s name in the signature, then you have to document it and make it public, users like to know what your API is returning to them. However, I agree that ergonomics for this are bad due to reimplementing traits, this thread discusses how to improve ergonomics for cases like this.


#7

“For that to work you also would have to obscure the documentation of your API and close the source, to make it difficult for the user to figure out that getThing produces a This value and flipThing then produces a That”

What? None of that is true, and the same logic would apply to private functions and private fields of a struct, if it were. You document the operations on the type, and interested parties can browse the source all they like, just as you can browse the source of Haskell modules that export types but not data constructors. You don’t need obfuscation because the compiler won’t let you reference the un-exported data constructors. And indeed, in Haskell if you put the type in the signature then the type is public, but you don’t have to make the variants / the data constructors public too.


#8

Hmm, so maybe you want private enum variants? Like what this question wanted?


#9

Yes, or rather, I want to know why I can’t have them. (The answer to that question says “enums are purely public”, but I want to know why they’re purely public.)


#10

By that answer, it seems most use cases are considered unidiomatic. So you could have it, but in general it’s not considered something worth having.


#11

Private enum variants used to exist be were removed about a year and a half ago: https://github.com/rust-lang/rfcs/blob/master/text/0026-remove-priv.md


#12

The idea of having an opaque type whose enum-ness is a private detail is appealing.

However, I don’t think that the wrapper struct would be too much bother. You can write impls on the struct type, and just have to pattern match on self.0 instead of self in methods.