donqo88682 2016-05-23 07:29
浏览 63
已采纳

创建一个常量类型并限制类型的值

I have a question about types of constants which are restricted to certain values and how you accomplish that in Go. 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?

  • 写回答

2条回答 默认 最新

  • dqab0824 2016-05-23 08:28
    关注

    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
    ", p, p)
    
    p = 3
    fmt.Printf("%v %d
    ", 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 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
    

    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 truly 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
    ", p, p)
    
    p = MyUn{p}
    fmt.Printf("%v %d
    ", 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.

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论
查看更多回答(1条)

报告相同问题?

悬赏问题

  • ¥15 有没有可以帮我搞一个微信建群链接,包括群名称和群资料群头像那种,不会让你白忙
  • ¥15 stm32开发clion时遇到的编译问题
  • ¥15 lna设计 源简并电感型共源放大器
  • ¥15 如何用Labview在myRIO上做LCD显示?(语言-开发语言)
  • ¥15 Vue3地图和异步函数使用
  • ¥15 C++ yoloV5改写遇到的问题
  • ¥20 win11修改中文用户名路径
  • ¥15 win2012磁盘空间不足,c盘正常,d盘无法写入
  • ¥15 用土力学知识进行土坡稳定性分析与挡土墙设计
  • ¥70 PlayWright在Java上连接CDP关联本地Chrome启动失败,貌似是Windows端口转发问题