I have a question about types of constants which are restricted to certain values and how you accomplish that in Golang. Say I create a type unary
which has two constant values Positive(1)
and Negative(-1)
and I want to restrict the user of that type (unary
) from creating other values of type unary
. Do I achieve this by creating a package and making the values Positive
and Negative
visible and making the type unary
restricted to the containing package? See code below for example
package unary
type unary int////not visible outside of the package unary
const (
Positive unary = 1//visible outside of the package unary
Negative unary = -1//visible outside of the package unary
)
func (u unary) String() string {//visible outside of the package unary
if u == Positive {
return "+"
}
return "-"
}
func (u unary) CalExpr() int {//visible outside of the package unary
if u == Positive {
return 1
}
return -1
}
Is this the correct way to restrict a type to certain constant values?
If you like to just work with
int
without introducing a wrapper type: a classic way to do that in Go is using a public interface with a private function; so everybody can use it but nobody can implement it; like:Others are able to set
Positive
tonil
though; but that's not a thing in Go world - in such cases.As @icza mentioned, one can overwrite public methods. But for private methods, Go will not call "the most shallow" one, but instead calls the original.
Flaws
Your proposed solution is not safe in a way you want it to be. One can use untyped integer constants to create new values of
unary
having a differentint
value than1
or-1
. See this example:Output will be:
We could change
p
's value to store theint
value3
which is obviously not equal toPositive
nor toNegative
. This is possible because Spec: Assignability:3
is an untyped constant, and it is representable by a value of typeunary
which has underlying typeint
.In Go you can't have "safe" constants of which "outsider" packages cannot create new values of, for the above mentioned reason. Because if you want to declare constants in your package, you can only use expressions that have "untyped" versions - which may be used by other packages too in assignments (just as in our example).
Unexported struct
If you want to fulfill the "safe" part, you may use unexported
struct
s, but then they cannot be used in constant declarations.Example:
Attempting to change its value:
Note that since we're now using a
struct
, we can further simplify our code by adding thestring
representation of our values to thestruct
:Note that this solution still has a "flaw": it uses exported global variables, whose values can be changed by other packages. It's true that other packages cannot create and assign new values, but they can do so with existing values, e.g.:
If you want to protect yourself from such misuse, you also have to make such global variables unexported. And then of course you have to create exported functions to expose those values, for example:
Then acquiring/using the values:
Interface
Care must be taken if you plan to use an interface type for your "constants". An example can be seen in Kaveh Shahbazian's answer. An unexported method is used to prevent others from implementing the interface, giving you the illusion that others truely can't implement it:
This is not the case however. With a dirty trick, this can be circumvented. The exported
Unary
type can be embedded, and an existing value can be used in order to implement the interface (along with the unexported method), and we can add our own implementations of the exported methods, doing / returning whatever we want to.Here is how it may look like:
Testing it:
Output:
Special case
As Volker mentioned in his comment, in your special case you could just use
As the type
bool
has two possible values:true
andfalse
, and we've used all. So there are no other values that could be "exploited" to create other values of our constant type.But know that this can only be used if the number of constants is equal to the number of possible values of the type, so the usability of this technique is very limited.
Also keep in mind that this does not prevent such misuses when a type of
unary
is expected, and someone accidentally passes an untyped constant liketrue
orfalse
.