Golang:创建常量类型并限制类型的值

G41*_*143 24 constants go

我有一个关于常量类型的问题,这些常量限制在某些值以及如何在Golang中实现.说我创建类型unary,其具有两个常数值Positive(1)Negative(-1)与我想限制该类型(用户unary从创建类型的其它值)unary.我是通过创建一个包并使值PositiveNegative可见并使类型unary限制为包含来实现这一目的吗?请参阅下面的代码

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
}
Run Code Online (Sandbox Code Playgroud)

这是将类型限制为某些常量值的正确方法吗?

icz*_*cza 31

缺陷

您提出的解决方案并不是您想要的安全方式.可以使用无类型的整数常量来创建unary具有int1or 不同的值的新值-1.看这个例子:

p := unary.Positive
fmt.Printf("%v %d\n", p, p)

p = 3
fmt.Printf("%v %d\n", p, p)
Run Code Online (Sandbox Code Playgroud)

输出将是:

+ 1
- 3
Run Code Online (Sandbox Code Playgroud)

我们可以改变p的值存储int3,这显然不等于Positive也没有Negative.这是可能的,因为Spec:Assignability:

的值x分配可变型的T(" x是分配给T")在任何这些情况中:

  • ...
  • x是一个无类型的常量,可以用类型的表示T.

3是一个无类型常量,它可以由unary具有底层类型的类型值表示int.

在Go中,由于上述原因,您不能拥有"安全"常量,其中"局外人"包不能创建新值.因为如果要在包中声明常量,则只能使用具有"无类型"版本的表达式 - 其他包也可以在赋值中使用(就像在我们的示例中一样).

未经移植的结构

如果要实现"安全"部分,可以使用未导出的structs,但是它们不能用于常量声明.

例:

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
}
Run Code Online (Sandbox Code Playgroud)

试图改变它的价值:

p := unary.Positive

p.val = 3 // Error: p.val undefined (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
Run Code Online (Sandbox Code Playgroud)

请注意,由于我们现在使用a struct,我们可以通过将string值的表示添加到以下内容来进一步简化代码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 }
Run Code Online (Sandbox Code Playgroud)

请注意,此解决方案仍然存在"缺陷":它使用导出的全局变量,其值可以由其他包更改.确实,其他包不能创建和分配值,但是它们可以使用现有值来实现,例如:

unary.Positive = unary.Negative
Run Code Online (Sandbox Code Playgroud)

如果你想保护自己免受这种滥用,你还必须使这些全局变量不被移植.当然,您必须创建导出的函数来公开这些值,例如:

var (
    positive = unary{1}
    negative = unary{-1}
)

func Positive() unary { return positive }

func Negative() unary { return negative }
Run Code Online (Sandbox Code Playgroud)

然后获取/使用值:

p := unary.Positive()
Run Code Online (Sandbox Code Playgroud)

接口

如果计划为"常量"使用接口类型,则必须小心.在Kaveh Shahbazian的回答中可以看到一个例子.一个未导出的方法用于防止其他人实现该接口,给你一个其他人真正无法实现它的错觉:

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 { /* ... */ }
Run Code Online (Sandbox Code Playgroud)

但情况并非如此.有了肮脏的把戏,这可以被规避.Unary可以嵌入导出的类型,并且可以使用现有值来实现接口(以及未导出的方法),并且我们可以添加我们自己的导出方法的实现,执行/返回我们想要的任何内容.

这是它的样子:

type MyUn struct {
    unary.Unary
}

func (m MyUn) String() string { return "/" }

func (m MyUn) CalExpr() int { return 3 }
Run Code Online (Sandbox Code Playgroud)

测试它:

p := unary.Positive
fmt.Printf("%v %d\n", p, p)

p = MyUn{p}
fmt.Printf("%v %d\n", p, p.CalExpr())
Run Code Online (Sandbox Code Playgroud)

输出:

+ 1
/ 3
Run Code Online (Sandbox Code Playgroud)

特殊情况

正如Volker在他的评论中提到的,在你的特殊情况下你可以使用

type unary bool

const (
    Positive unary = true
    Negative unary = false
)
Run Code Online (Sandbox Code Playgroud)

由于类型bool有两个可能的值:truefalse,我们已经使用了全部.因此,没有其他值可以"利用"来创建常量类型的其他值.

但是要知道,只有当常数的数量等于该类型的可能值的数量时才能使用,因此该技术的可用性非常有限.

还要记住,当unary预期某种类型时,这并不会阻止此类误操作,并且有人意外地传递了类似于true或的无类型常量false.