[Go]1.Constant and Enum

Intro

Go is a strongly typed and statically typed language, which will check types in compile time and doesn't allow implict typ conversion. In C we might have:

unsigned int u = 1e9;
long signed int i = -1;
... i + u ...

The result of the above code snippet will depend on the specifications of different compilers, which can be nasty to debug with. In Go if we have:

var u uint = 1e9
var i int = -1
i + u // Error
uint(i) + u // OK
i + int(u) // OK

This checks will happen on variables, but what about constants. In Go we can have untyped constants which can bypass this check.

Constant

Untyped Constant

In Go, we can use the keyword const to define a constant. A constant is an introduced name for a value. For example:

const typedHello string = "Hello, 世界"

By specifying string here, we expicitly say that this constant is a string. We can omit this part to define an untyped constant.

const hello = "Hello, 世界"

We can assign the typed constant value to a variable with the same type, the following behavior is allowed:

var s string
s = typedHello
fmt.Println(s)

But this is not allowed, since type alias in Go is regarded as a different type:

type MyString string
var m MyString
m = typedHello // Type error
fmt.Println(m)

By forcing a type conversion we can do the assignment:

m = MyString(typedHello)
fmt.Println(m)

Assigning the a untyped constant or a string literal is fine:

m = hello  // OK
m = "Hello, 世界" // Also OK

I think an untyped constant works just the same as a string literal, so the untyped constant purely introduced a naming on a literal, no other constraints are added. And typed constants obey all the rules of typed values in Go.

Default Type

In Go, we have three different methods to declare a new variable, and two of them don't require us to specify a type:

str := "Hello, 世界"
var str = "Hello, 世界"
var str string = "Hello, 世界"

This is because that each literal value has a default type, here "Hello, 世界" obiviously has a default type string. If we don't want to use a default type, say we want to assign 8 to an uint value, and 8's default type is int, we can choose one of the following ways:

var u uint = 8
var u = uint(8)
u := uint(8)

If a number literal's value is out of range of a specified type, Go won't do implicit conversion for you but report an error:

var i8 int8 = 128 // Error: too large.
var u8 uint8 = -1 // Error: negative value.
type Char byte
var c Char = '世' // Error: '世' has value 0x4e16, too large.
const MaxUint uint = -1 // Error: negative value
const MaxUint uint = uint(-1) // Error: negative value
const MaxUint uint = ^0 // Error: overflow

But a negative variable can be explicitly converted to an unsigned integer:

var u uint
var v = -1
u = uint(v)

Enum

Overview

We can use a group of constants to represent enum:

const (
    Yellow = 0
    Red = 1
    Blue = 2
)

But this is hard to maintain the values. If we want to insert a value before Red we need to change Red and Blue's value too. Luckily, in Go we have iota to deal with this problem.

iota Basics

If we use iota, the code snippet will be:

const (
    Yellow = iota // 0
    Red           // 1
    Blue          // 2
)

If we want to insert a new value, we can simply add the name to the expected position:

const (
    Yellow = iota // 0
    Pink          // 1
    Red           // 2
    Blue          // 3
)

iota record the counts of names within a list of a const declaration, it can only be used in const declaration. In a list of const declaration, if a name is not assigned with a value, it will adopt the value of the last declaration. The above code snippet is equivalent to:

const (
    Yellow = iota // 0
    Pink = iota   // 1
    Red = iota    // 2
    Blue = iota   // 3
)

iota is not increased only if it's called, it's increased by every line of const declaration. Let's see this example:

const (
        a = iota      // 0
        b             // 1
        c             // 2
        d = "hello"   // individual value "hello", iota += 1
        e             // "ha", iota += 1
        f = 100       // 100, iota +=1
        g             // 100  iota +=1
        h = iota      // 7
        i             // 8
)

Another example:

    a = iota // 0
    b = 10   // 10
    c        // 10
    d, e = iota, iota // 3, 3
    f = iota // 4

Remember, iota is increased only if we start a new line, so d and e are both 3 in this case. Using a different list of declaration will reset iota:

const (
   a = iota      // 0
   b int = iota  // 1
)
const (
   c = iota      // 1
)

Iterate

There is no out-of-the-box method of iterate over the enum values created by a list of const declaration in Go, but we can use this:

type Dir int

const (
    NORTH Dir = iota
    NORTHEAST
    EAST
    SOUTHEAST
    SOUTH
    SOUTHWEST
    WEST
    NORTHWEST
    dirLimit // this will be the last Dir + 1
)
for dir := Dir(0); dir < dirLimit; dir++ {
    // do what you want
}

Or we can create an extra array:

var Weekdays = []time.Weekday{
    time.Sunday,
    time.Monday,
    time.Tuesday,
    time.Wednesday,
    time.Thursday,
    time.Friday,
    time.Saturday,
}

func main() {
    for _, day := range Weekdays {
            fmt.Println(day)
    }
}

Advanced Operation

Skip Value

const (
    C1 = iota + 1
    _
    C3
    C4
)
fmt.Println(C1, C3, C4) // "1 3 4"

Use with String

type Direction int

const (
    North Direction = iota
    East
    South
    West
)

func (d Direction) String() string {
    return [...]string{"North", "East", "South", "West"}[d]
}

Mixed with Computation

const (
    _         = iota        // 0 - int
    a         = iota + 10   // 11 - int
    b int     = iota * 10   // 20 - int
    c float64 = iota + 5.1  // 8.1 - float64
)

Create Bitmask

const (
    Secure = 1 << iota // 0b001
    Authn              // 0b010
    Ready              // 0b100
)

Reference

  1. Constants - The Go Blog
  2. iota — Create Effective Constants in Golang
  3. The Go Programming Language Specification
  4. Stackoverflow
  5. 4 iota enum examples