Golang:创建一个常量类型并限制类型的值 [英] Golang: Creating a Constant Type and Restricting the Type's Values
问题描述
我有一个关于常量类型的问题,它们限制在某些值以及你如何在Golang中完成。假设我创建了一个一元
类型,它有两个常量值 Positive(1)
和 Negative -1)
,我想限制该类型的用户( unary
)创建 unary
。我是否可以通过创建一个包并使值 Positive
和 Negative
可见并使类型为一元
限于包含的包?例如见下面的代码
包一元
类型一元诠释////不可见包一元
$ b $常量(
正一元= 1 //可见于包之外一元
负一元= -1 //在包之外可见一元
)
func(u一元)字符串()字符串{// //在包名外可见一元
if u ==正数{
return+
}
return -
func(u一元)CalExpr()int {//在包名外可见一元
if u == Positive {
return 1
}
return -1
}
这是限制一个类型到某些常量值的正确方法吗?
缺陷
您提出的解决方案在您希望的方式下不安全。可以使用非类型化的整型常量来创建具有不同于 int
值的一元
的新值,而不是 1
或 -1
。看到这个例子:
p:= unary.Positive
fmt.Printf(%v%d \\\
,p,p)
p = 3
fmt.Printf(%v%d\\\
,p,p)
输出结果为:
+ 1
- 3
我们可以更改 p
'用于存储 int
值 3
,这显然不等于正值
也不是负值
。这是可能的,因为规格:可分配性:
值
x
是可分配给 T (x
)的 rel =nofollow noreferrer可在以下任何情况下分配给T
):
- ...
x
是一个非类型化的常量可以用类型T
的值表示。
$ b $ 3
是一个无类型常量,它可以用类型 unary
,它具有基础类型 int
。
在Go中,您不能拥有安全常量出于上述原因,局外人套餐无法创造新的价值。因为如果你想在你的包中声明常量,你只能使用具有无类型版本的表达式 - 其他包也可以在赋值中使用这些表达式(就像我们的例子一样)。 p>
未引用的结构
如果您想完成安全部分,您可以使用未导出的 struct
s,但不能在常量声明中使用它们。
示例:
类型一元结构{
val int
var(
Positive =一元{1}
Negative =一元{-1}
)
func(u unary)String()string {
if u == Positive {
return+
}
return -
}
func(u一元)CalExpr()int {
返回u.val
}
尝试更改其值:
p:=一元。正负
p.val = 3 //错误:p.val undefied(不能引用未导出的字段或方法val)
p = unary.unary {3} //错误:无法引用未导出的名称unary.unary
//同样的错误:在unary.unary文字中隐式赋值未导出的字段'val'
$ b $注意由于我们现在正在使用 struct
,我们可以通过添加字符串
将值表示为 struct
:
$ b
type unary struct {
val int
str string
$ b var
Positive =一元{1,+}
Negative =一元{ - 1, - }
)
func(u一元)String()字符串{返回u.str}
func(一元)CalExpr() int {return u.val}
请注意,这个解决方案仍然存在一个缺陷全局变量,其值可以被其他包改变。确实,其他软件包无法创建和分配新的值,但可以使用现有值执行此操作,例如:
unary.Positive = unary.Negative
如果你想保护自己免受这种滥用,你也必须使这样的全局变量不被导出。然后当然,你必须创建导出的函数来公开这些值,例如:
var(
positive =一元{1}
负=一元{-1}
)
func Positive()一元{返回正值}
func Negative {return negative}
然后获取/使用这些值:
p:= unary.Positive()
Interface
如果您打算为常数使用接口类型,则必须小心。在Kaveh Shahbazian的回答中可以看到一个例子。一个未导出的方法被用来阻止其他人实现接口,给你一个错觉,其他人真的不能实现它:
类型一元接口{
fmt.Stringer
CalExpr()int
disabler()//在此包外部实现此接口已禁用
}
var (
Positive Unary = unary(1)//在包名外可见一元
Negative一元=一元(-1)//在包名外可见一元
)
类型一元整型//在包之外不可见
func(u一元)disabler(){}
func(u一元)String()string {/ * ... * /}
func(u一元)CalExpr()int {/ * ... * /}
然而情况并非如此。有一个肮脏的伎俩,这可以绕过。可以嵌入导出的 Unary
类型,并且可以使用现有的值来实现接口(以及未导出的方法),并且可以添加我们自己的导出的方法,做/返回任何我们想要的。
以下是它的样子:
类型MyUn结构{
unary.Unary
}
func(m MyUn)String()string {return/}
func(m MyUn)CalExpr()int {return 3}
测试它:
p:= unary.Positive
fmt.Printf(%v%d \\\
,p ,p)
p = MyUn {p}
fmt.Printf(%v%d \\\
,p,p.CalExpr())
$ c
$ b $输出:
$ b $ pre $ + 1
/ 3
特殊情况
Volker在他的评论中提到,在你的特例中,你可以使用
类型unary bool
常量(
正数unary = true
否定一元= false
)
c $ c> bool 有两个可能的值: true
和 false
,我们'已经全部使用了。所以没有其他值可以被利用来创建我们常量类型的其他值。
但是要知道,只有当常量的数量是等于该类型的可能值的数量,所以这种技术的可用性是非常有限的。
请记住,这并不能防止这种误用时,类型 unary
是预期的,并且有人不小心传递了一个非类型常量,如 true
或 false
。
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?
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 different int
value than 1
or -1
. See this example:
p := unary.Positive
fmt.Printf("%v %d\n", p, p)
p = 3
fmt.Printf("%v %d\n", p, p)
Output will be:
+ 1
- 3
We could change p
's value to store the int
value 3
which is obviously not equal to Positive
nor to Negative
. This is possible because Spec: Assignability:
A value
x
is assignable to a variable of typeT
("x
is assignable toT
") in any of these cases:
- ...
x
is an untyped constant representable by a value of typeT
.
3
is an untyped constant, and it is representable by a value of type unary
which has underlying type int
.
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:
type unary struct {
val int
}
var (
Positive = unary{1}
Negative = unary{-1}
)
func (u unary) String() string {
if u == Positive {
return "+"
}
return "-"
}
func (u unary) CalExpr() int {
return u.val
}
Attempting to change its value:
p := unary.Positive
p.val = 3 // Error: p.val undefied (cannot refer to unexported field or method val)
p = unary.unary{3} // Error: cannot refer to unexported name unary.unary
// Also error: implicit assignment of unexported field 'val' in unary.unary literal
Note that since we're now using a struct
, we can further simplify our code by adding the string
representation of our values to the struct
:
type unary struct {
val int
str string
}
var (
Positive = unary{1, "+"}
Negative = unary{-1, "-"}
)
func (u unary) String() string { return u.str }
func (u unary) CalExpr() int { return u.val }
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.:
unary.Positive = unary.Negative
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:
var (
positive = unary{1}
negative = unary{-1}
)
func Positive() unary { return positive }
func Negative() unary { return negative }
Then acquiring/using the values:
p := unary.Positive()
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:
type Unary interface {
fmt.Stringer
CalExpr() int
disabler() // implementing this interface outside this package is disabled
}
var (
Positive Unary = unary(1) // visible outside of the package unary
Negative Unary = unary(-1) // visible outside of the package unary
)
type unary int // not visible outside of the package unary
func (u unary) disabler() {}
func (u unary) String() string { /* ... */ }
func (u unary) CalExpr() int { /* ... */ }
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:
type MyUn struct {
unary.Unary
}
func (m MyUn) String() string { return "/" }
func (m MyUn) CalExpr() int { return 3 }
Testing it:
p := unary.Positive
fmt.Printf("%v %d\n", p, p)
p = MyUn{p}
fmt.Printf("%v %d\n", p, p.CalExpr())
Output:
+ 1
/ 3
Special case
As Volker mentioned in his comment, in your special case you could just use
type unary bool
const (
Positive unary = true
Negative unary = false
)
As the type bool
has two possible values: true
and false
, 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 like true
or false
.
这篇关于Golang:创建一个常量类型并限制类型的值的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!