Golang:创建一个常量类型并限制类型的值 [英] Golang: Creating a Constant Type and Restricting the Type's Values

查看:319
本文介绍了Golang:创建一个常量类型并限制类型的值的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个关于常量类型的问题,它们限制在某些值以及你如何在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 ):



$ 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())


$ 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 type T ("x is assignable to T") in any of these cases:

  • ...
  • x is an untyped constant representable by a value of type T.

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 structs, 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆