Golang: créer un Type Constant et restreindre les valeurs du Type

j'ai une question sur les types de constantes qui sont limitées à certaines valeurs et comment vous l'accomplissez à Golang. Disons que je crée un type unary qui a deux valeurs constantes Positive(1) et Negative(-1) et je veux restreindre l'utilisateur de ce type (unary) à partir de la création d'autres valeurs de type unary. Est-ce que j'obtiens ceci en créant un paquet et en faisant les valeurs Positive et Negative visible et de faire le type unary restreint au paquet contenant? Voir code ci-dessous exemple

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
}

est-ce la bonne façon de restreindre un type à certaines valeurs constantes?

16
demandé sur icza 2016-05-23 10:29:58

2 réponses

Défauts

la solution que vous proposez n'est pas sécuritaire comme vous le souhaitez. On peut utiliser non constantes entières à créer de nouvelles valeurs de unary différent int valeur de 1 ou -1. Voir cet exemple:

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

p = 3
fmt.Printf("%v %d\n", p, p)

Sortie sera:

+ 1
- 3

changer p'la valeur à stocker l' int valeur 3 qui n'est évidemment pas égal à Positive ni Negative. Cela est possible parce que Spec: Cessibilité:

valeur xassignables variable de type T ("x est assignable à T") dans l'un de ces cas:

  • ...
  • x est un non constant représentable par une valeur de type T.

3 est une constante non typée, et elle peut être représentée par une valeur de type unary qui a le type sous-jacent int.

dans Go vous ne pouvez pas avoir de constantes "sûres" dont les paquets "extérieurs" ne peuvent pas créer de nouvelles valeurs de, pour la raison mentionnée ci-dessus. Parce que si vous voulez déclarer constantes dans votre paquet, vous ne pouvez utiliser que des expressions qui ont des versions "non typées" - qui peuvent être utilisées par d'autres paquets aussi dans les assignations (comme dans notre exemple).

structure inexploitée

si vous voulez remplir la partie "sûr" , vous pouvez utiliser désexporter structs, mais alors ils ne peuvent pas être utilisés dans déclarations de constantes.

Exemple:

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
}

la Tentative de modification de sa valeur:

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

notez que puisque nous utilisons maintenant un struct on peut simplifier notre code en ajoutant le string représentation de nos valeurs à l' 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 }

notez que cette solution a toujours un" défaut": elle utilise des variables globales exportées, dont les valeurs peut être modifié par d'autres paquets. Il est vrai que les autres paquets ne peuvent pas créer et assigner valeurs, mais ils peuvent le faire avec les valeurs existantes, par exemple:

unary.Positive = unary.Negative

si vous voulez vous protéger d'une telle utilisation abusive, vous devez aussi faire en sorte que ces variables globales ne soient pas reproduites. Et puis bien sûr, vous devez créer des fonctions exportées afin de dénoncer ces valeurs, par exemple:

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

func Positive() unary { return positive }

func Negative() unary { return negative }

puis l'acquisition / l'utilisation du valeurs:

p := unary.Positive()

Interface

précautions doivent être prises si vous prévoyez d'utiliser un type d'interface pour votre "constantes". Un exemple peut être vu dans la réponse de Kaveh Shahbazian. Une méthode non utilisée est utilisée pour empêcher les autres d'implémenter l'interface, vous donnant l'illusion que les autres ne peuvent pas l'implémenter:

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 { /* ... */ }

Ce n'est pas le cas cependant. Avec un sale tour, cela peut être contourné. Le exporté Unary type peut être intégré, et un la valeur existante peut être utilisée pour implémenter l'interface (avec la méthode non-triée), et nous pouvons ajouter nos propres implémentations des méthodes exportées, en faisant / retournant tout ce que nous voulons.

Voici à quoi ça peut ressembler à ça:

type MyUn struct {
    unary.Unary
}

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

func (m MyUn) CalExpr() int { return 3 }

Test:

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

p = MyUn{p}
fmt.Printf("%v %d\n", p, p.CalExpr())

Sortie:

+ 1
/ 3

cas particulier

Comme Volker a mentionné dans son commentaire, dans votre cas particulier, vous pourriez juste utilisez

type unary bool

const (
    Positive unary = true
    Negative unary = false
)

comme le type bool a deux valeurs possibles: true et false, et nous avons tout utilisé. Donc, il n'y a pas d'autres valeurs que pourrait être "exploités" pour créer d'autres valeurs de notre constante.

mais sachez que ceci ne peut être utilisé que si le nombre de constantes est égal au nombre de valeurs possibles du type, de sorte que l'utilisabilité de cette technique est très limitée.

gardez également à l'esprit que cela n'empêche pas de tels abus lorsqu'un type de unary est attendu, et quelqu'un dépasse accidentellement une constante non typée comme true ou false.

25
répondu icza 2018-06-20 13:11:12

Si vous voulez juste de travailler avec int sans introduire un type de wrapper: une façon classique de le faire dans Go est d'utiliser une interface publique avec une fonction privée; donc tout le monde peut l'utiliser mais personne ne peut l'implémenter; comme:

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 { //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
}

D'autres peuvent définir Positivenil cependant; mais ce N'est pas une chose dans Go world - dans de tels cas.

1
répondu Kaveh Shahbazian 2016-05-23 12:10:24