What is an enum?
How to create an enum with Go.
What is iota, and how to use it?
What are bitwise operators, and how to use them?
Enum
Enumeration type
iota
index
Byte, bits
Binary
Bitmask, Bitflag
An “enum” (or enumeration data type) is a data type that consists of a set of “values that are explicitly defined by the programmer”
For example, days of the week are an enum. There are seven days of the week and not more.
\n\nThe language (at least in its version 1) does not have specific enumeration types. However, we can still build a type that offers the same features as enums.
\n\nLet’s take the example of HTTP methods.
\n// enum-iota-bitmasks/type-as-enum/main.go \n\ntype HTTPMethod int\n\nconst (\n GET HTTPMethod = 0\n POST HTTPMethod = 1\n PUT HTTPMethod = 2\n DELETE HTTPMethod = 3\n PATCH HTTPMethod = 4\n HEAD HTTPMethod = 5\n OPTIONS HTTPMethod = 6\n TRACE HTTPMethod = 7\n CONNECT HTTPMethod = 8\n)
\nFirst, we declare a new type HTTPMethod
(its underlying type is int
).
Then we create nine constants of type HTTPMethod
Each constant is of type HTTPMethod
and is named like the HTTP method its designates.
You are free to use an underlying type different from int
.
However, this is a common practice to use integers.
\nIt’s generally more efficient to compare an integer with another integer instead of comparing a string with another string
You can use iota. (see next sections)
Imagine that you have a function that handles HTTP requests.
\nIn this function, you expect the user to give you an HTTP method, and you expect to handle a predefined set of methods. Your function can have a parameter of type HTTPMethod
(instead of int
) :
func handle(method HTTPMethod, headers map[string]string, uri string) {}
\nInside the function body, you can adapt the behavior of your handler by using the enum values :
\nfunc handle(method HTTPMethod, headers map[string]string, uri string) {\n if method == GET {\n fmt.Println("the method is get")\n } else {\n fmt.Println("the method is not get")\n }\n}
\nEnums types can also be used in structs (like any other types) :
\n// enum-iota-bitmasks/type-as-enum/main.go \n\ntype HTTPRequest struct {\n method HTTPMethod\n headers map[string]string\n uri string\n}\nfunc main() {\n r := HTTPRequest{method: GET, headers: map[string]string{"Accept": "application/json"}, uri: "/prices"}\n fmt.Println(r)\n}
\n\nThe solution proposed in the previous section is not perfect. Here is why :
\nint
can be a variable of type HTTPMethod
:lolMethod := HTTPMethod(42)\nheaders := make(map[string]string)\nhandle(lolMethod,headers,"/prices" )
\nWe created a variable lolMethod
which is of type HTTPMethod
and the code will compile.
HTTPMethod
the output is an int
:log.Println(GET)\n// > 0
\nHTTPMethod
will be printed as an int ...type HTTPRequest struct {\n Method HTTPMethod `json:"method"`\n Headers map[string]string `json:"headers"`\n Uri string `json:"uri"`\n}\nr := HTTPRequest{Method: GET, Headers: map[string]string{"Accept": "application/json"}, Uri: "/prices"}\nmarshaled, err := json.Marshal(r)\nif err != nil {\n panic(err)\n}
\nWill produce the following JSON string:
\n{\n "method": 0,\n "headers": {\n "Accept": "application/json"\n },\n "uri": "/prices"\n}
\nThe value 0
might be interpreted as an error. Instead, we want to output \"GET\"
.
jsonB := []byte("{\\"method\\":\\"GET\\",\\"headers\\":{\\"Accept\\":\\"application/json\\"},\\"uri\\":\\"/prices\\"}")\nreq := HTTPRequest{}\nerr = json.Unmarshal(jsonB,&req)\nif err != nil {\n panic(err)\n}
\nWe got a panic :
\npanic: json: cannot unmarshal string into Go struct field HTTPRequest.method of type main.HTTPMethod
\nThis is perfectly normal, we have a string as input, and we want to convert it to an int. One solution could be to change the underlying type to string.
\n\nTo solve the previous issues :
\nWe need to be able to check if a value is part of the enum
\nWe need to be able to print an element correctly
\nfmt.Stringer
interfaceWe need to be able to marshal correctly an enum value into JSON (an maybe into another format)
\njson.Marshaler
interfaceWe need to be able to unmarshal correctly an enum value from JSON (an maybe from another format)
\njson.Unmarshaler
interfaceWe can implement those interfaces :
\n// enum-iota-bitmasks/enum-implementations/main.go \n\ntype HTTPMethod int\n\nfunc (h HTTPMethod) IsValid() bool {\n panic("implement me")\n}\n\nfunc (h HTTPMethod) String() string {\n panic("implement me")\n}\n\nfunc (h HTTPMethod) UnmarshalJSON(bytes []byte) error {\n panic("implement me")\n}\n\nfunc (h HTTPMethod) MarshalJSON() ([]byte, error) {\n panic("implement me")\n}
\nHowever, this is tedious. We can use a library to do that for us. After a quick search on GitHub, it seems that two libraries emerge :
\nhttps://github.com/alvaroloes/enumer
https://github.com/abice/go-enum
They will generate those methods for you. They offer a command-line interface.
\nNote that the support for enums is a feature request for version 2 of the language.
\n\nIn the previous section:
\nWe needed to assign an integer to each constant.
The type HTTPMethod
is also written on each line.
To avoid those two tasks, we can modify our enum to use iota
:
// enum-iota-bitmasks/iota/main.go\npackage main\n\nimport "fmt"\n\ntype HTTPMethod int\n\nconst (\n GET HTTPMethod = iota\n POST HTTPMethod = iota\n PUT HTTPMethod = iota\n DELETE HTTPMethod = iota\n PATCH HTTPMethod = iota\n HEAD HTTPMethod = iota\n OPTIONS HTTPMethod = iota\n TRACE HTTPMethod = iota\n CONNECT HTTPMethod = iota\n)\n\nfunc main() {\n fmt.Println(PUT)\n // 2\n}
\nWe can simplify this code. It’s possible to avoid repeating HTTPMethod = iota
each time by using the implicit repetition property. This property is very handy; it says that inside a constant set (const(...)
) you can assign a value just to the first const:
// enum-iota-bitmasks/iota-improvement/main.go \n\nconst (\n GET HTTPMethod = iota\n POST\n PUT\n DELETE\n PATCH\n HEAD\n OPTIONS\n TRACE\n CONNECT\n)
\n\n“iota represents successive untyped integer constants”
It means that iota is always an integer; it’s impossible to use iota to construct floats values (for instance).
“Its value is the index of the respective ConstSpec in that constant declaration”
The value of iota is determined by the index of the constant in the constant declaration. In the previous example, POST is the second constant then it has a value of 1. Why 1? Why not 2? That’s because of the third property of iota :
\nThe initial value of iota is zero. Example :
\ntype TestEnum int\n\nconst (\n First TestEnum = iota\n Second\n)\n\nfmt.Println(First)\n// 0
\niota is initialized with zero. If you want that your first enum element starts with something other than zero, you can assign a different value to the initial constant :
\ntype TestEnum int\n\nconst (\n First TestEnum = iota +4\n Second\n)\n\nfmt.Println(First)\n// 4 (0+4)
\nYou can also multiply iota by an integer :
\ntype TestEnum int\n\nconst (\n First TestEnum = iota * 3\n Second\n)\n\nfmt.Println(Second)\n// 3 (1*3)
\nA byte is composed of 8 bits of memory. A bit is a binary digit. It’s either equal to 0 or 1.
\nThe indexing of bits is not natural; we start counting from right to left (and not left to right).
\n\nThe first step before jumping to bitwise operators is to learn how to print the binary representation of a number.
\n\nTo print the bits of a number, you can use the fmt.Printf() function with the format specifier %08b.08 means that we will print the binary value on 8 bits.
\n// enum-iota-bitmasks/print-bits/main.go\npackage main\n\nimport "fmt"\n\nfunc main() {\n var x uint8\n x = 1\n fmt.Printf("%08b\\n", x)\n //00000001\n x = 2\n fmt.Printf("%08b\\n", x)\n //00000010\n x = 255\n fmt.Printf("%08b\\n", x)\n //11111111\n}
\nIn the previous code listing, we define X, an unsigned integer stored on 8 bits. X can take the values from 0 to 255. We assign to x the value 1, then 2, and 255. Each time we are printing the binary representation.
\n\nTo do operations on bits, we need operators. We call those operators “bitwise operators” In this section, we will go through all of them. Operations on bits can be intimidating initially, but they are nothing else than boolean logic. If you are already familiar with AND, OR, XOR, etc... it will be easy to understand.
\nAn operation can be decomposed into three elements :
\nThe operator
The operands
The result
For instance, in the operations x | y = z
x
and y
are operands
|
is the operator
z
is the result For each operator, the same logic applies. Operations are performed independently on each bit of the operands.
Bitwise operators do not have to be mixed up with logical operators (&&, || and !). Those operators are used to compare boolean values from left to right.
\n&
x
and y
are two bitsx & y
is equal to 1 only if x
and y
are equal to 1.
Otherwise x & y
is equal to 0. J
ust remember that it’s only true if both operands are true.
x | \ny | \nx&y | \n
---|---|---|
0 | \n0 | \n0 | \n
0 | \n1 | \n0 | \n
1 | \n1 | \n1 | \n
1 | \n0 | \n0 | \n
Let’s take an example :
\n// enum-iota-bitmasks/bitwise-operations/main.go \n\nvar x, y, z uint8\nx = 1 // 00000001\ny = 2 // 00000010\nz = x & y\n\n// print in binary\nfmt.Printf("%08b\\n", z)\n// 00000000\n\n// print in base 10\nfmt.Printf("%d\\n", z)\n// 0
\n3 integers variables are created (x
, y
, z
). Then we store x & y
in the variable z
.
As shown in figure 2 we take each bit individually, and then we are calculating the result of every; that’s single operation : 1 & 0 = 0
0 & 1 = 0
0 & 0 = 0
0 & 0 = 0
0 & 0 = 0
0 & 0 = 0
0 & 0 = 0
0 & 0 = 0
Note that we begin by the right side. Get used to it; that’s the rule when you manipulate bits.
Let’s take another example :
\n// enum-iota-bitmasks/bitwise-operation-and/main.go \n\nvar x, y, z uint8\nx = 200 // 11001000\nfmt.Printf("%08b\\n", x)\n\ny = 100 // 01100100\nfmt.Printf("%08b\\n", y)\n\nz = x & y\n\n// print in binary\nfmt.Printf("%08b\\n", z)\n// 01000000\n\n// print in base 10\nfmt.Printf("%d\\n", z)\n// 64
\nIn the figure 3 you can see the detail of the operation 200\\&100.
\n|
x | y
is equal to 1 if :
one of the operands is equal to 1
both operands are equal to 1.
x | \ny | \nx|y | \n
---|---|---|
0 | \n0 | \n0 | \n
0 | \n1 | \n1 | \n
1 | \n1 | \n1 | \n
1 | \n0 | \n1 | \n
Here is an example in go :
\n// enum-iota-bitmasks/bitwise-or/main.go \n\nvar x, y, z uint8\nx = 200 // 11001000\nfmt.Printf("%08b\\n", x)\n\ny = 100 // 01100100\nfmt.Printf("%08b\\n", y)\n\nz = x | y\n\n// print in binary\nfmt.Printf("%08b\\n", z)\n// 11101100\n\n// print in base 10\nfmt.Printf("%d\\n", z)\n// 236
\nIn the figure 4 you can see that we apply the or operation bit by bit. Starting from the right : 0 | 0 = 0
0 | 0 = 0
0 | 1 = 1
1 | 0 = 1
0 | 0 = 0
0 | 1 = 1
1 | 1 = 1
0 | 1 = 1
When we use the base ten notation for the operands and the result, it does not make sense. Let’s use the binary notation :
11001000 | 01100100 = 11101100
\nWith OR, we set to 1 the bits of the left operand that are set to 1 in the right operand. (you can switch left and right, and this will also work).
\n\n^
The XOR abbreviation stands for e Xclusive OR. Why do we need to add another or? Is the one that we defined before not sufficient? Remember that with OR we have the following result 1 | 1 = 1
.
When both operands are true, the result is true. Sometimes this behavior isn’t acceptable. We want maybe to exclude that case.
\nWith the exclusive or : 1 ^ 1 = 0
. To be more precise, we have the following rule that applies: x ^ y
is equal to 1 only if one of the operands is equal to 1, not both.
// enum-iota-bitmasks/bitwise-xor/main.go \n\nvar x, y, z uint8\nx = 200 // 11001000\nfmt.Printf("%08b\\n", x)\n\ny = 100 // 01100100\nfmt.Printf("%08b\\n", y)\n\nz = x ^ y\n\n// print in binary\nfmt.Printf("%08b\\n", z)\n// 10101100\n\n// print in base 10\nfmt.Printf("%d\\n", z)\n// 172
\nThe XOR set to 0 the bits that are identical in the two operands and set to 1 the bits that are different.
\n\n^
The NOT operator is the same as the XOR operator except that we use it in front of only one operand. It’s pretty easy to remember because this operator will reverse the values that will be stored by each bit :
\n^0 = 1
^1 = 0
NOT will invert the values of the bits. Let’s take an example :
\n// enum-iota-bitmasks/bitwise-not/main.go \n\nvar x, z uint8\nx = 200 // 11001000\nfmt.Printf("%08b\\n", x)\n\nz = ^x\n\n// print in binary\nfmt.Printf("%08b\\n", z)\n// 00110111\n\n// print in base 10\nfmt.Printf("%d\\n", z)\n// 55
\n&^
(bit clear) The AND NOT operator is used to set to zero the bits of the left operand if the corresponding bit in the right operand is set to 1. This might be obscure, so I suggest you to take a look at the figure 6.
\nThis operator mixes the operator AND and NOT. We can decompose it :
\nx AND NOT y
is equivalent to :
x AND (NOT y)
Let’s try to obtain the same result as before with this decomposition :
\nLet’s denote :
\nx = 11001000
y = 11111111
NOT y = 00000000
x AND NOT y
is equivalent to
11001000 AND 00000000 = 00000000
<<
, >>
) Those operators are used to move the bits to the left or to the right.
\nYou have to specify the number of positions by which the bits will be shifted.
\n\n// enum-iota-bitmasks/bitwise-left-shift/main.go \n\nvar x, n, z uint8\nx = 200 // 11001000\nfmt.Printf("%08b\\n", x)\n\n// number of positions\nn = 1\nz = x << n\n// print in binary\nfmt.Printf("%08b\\n", z)\n// 10010000\n\n// print in base 10\nfmt.Printf("%d\\n", z)\n// 144
\nIn the previous example, we are left-shifting the byte 11001000 by one position. The result of this shift is 10010000. We have added a zero at the left of the byte and shifted the other by 1 position as you can see in the figure 7.
\nWe are storing our integers into 8 bits, so we are losing bits. to avoid that, we can store our numbers on 16 bits (2 bytes) :
\nvar x, n, z uint16\nx = 200 // 11001000\nfmt.Printf("%08b\\n", x)\n\n// number of positions\nn = 1\nz = x << n\n// print in binary\nfmt.Printf("%b\\n", z)\n// 110010000\n\n// print in base 10\nfmt.Printf("%d\\n", z)\n// 400
\nYou can note something interesting; we have multiplied our number by 2.
\n200 << 1 = 400
\nWhen you left shift the binary representation of a number by n position you multiply it’s decimal representation bt it by two at the power n.
\nFor instance, if you left shift by 3, you will multiply your number by two at the power three, which is 8 :
\nvar x, n, z uint16\nx = 200 // 11001000\nfmt.Printf("%08b\\n", x)\n\n// number of positions\nn = 3\nz = x << n\n// print in binary\nfmt.Printf("%b\\n", z)\n// 11001000000\n\n// print in base 10\nfmt.Printf("%d\\n", z)\n// 1600
\n\nThe operator >> allow you to shifts the bits to the right :
\n// enum-iota-bitmasks/bitwise-right-shift/main.go \n\nvar x, n, z uint8\nx = 200 // 11001000\nfmt.Printf("%08b\\n", x)\n\n// number of positions\nn = 3\nz = x >> n\n// print in binary\nfmt.Printf("%08b\\n", z)\n// 00011001\n\n// print in base 10\nfmt.Printf("%d\\n", z)\n// 25
\nYou can see here that we are dividing 200 by 3 when we shift the bytes to the right. This is another property of binary numbers. When you left shift the binary representation of a number (in base 10) by n position, you divide it by two at the power n.
\n\nImagine that you are building a function that requires eight boolean values to be configured :
\nIs verbose mode activated?
Is configuration loaded from disk?
Is database connexion required?
Is logger activated?
Is debug mode activated?
Is support for float activated?
Is recovery mode activated?
Reboot on failure?
What we can do instead is passing just an integer value. This integer value can represent the configuration.
\nFor instance, 01110001 (113 in base 10) will represent the following configuration :
\nActivated (1)
Deactivated (0)
Deactivated (0)
Deactivated (0)
Activated (1)
Activated (1)
Activated (1)
Deactivated (0)
Remember that when bytes are involved, we read from right to left.
\nWhat are the advantages?
\nWe improve the code readability; we pass only an argument instead of 8
We spare memory. If we had eight booleans, we have 8*8 bits = 64 bits used compared to only 8.
We will use the left shift bitwise operation. The idea is the first configuration value is equal to 1 (00000001), and then for each new configuration variable, we will left shift the bit :
\ntype MyConf uint8\n\nconst (\n VERBOSE MyConf = 1 << iota\n CONFIG_FROM_DISK\n DATABASE_REQUIRED\n LOGGER_ACTIVATED\n DEBUG\n FLOAT_SUPPORT\n RECOVERY_MODE\n REBOOT_ON_FAILURE\n)
\nThe values that are generated by this constant declaration are the following :
\nVERBOSE : 00000001
CONFIG_FROM_DISK : 00000010
DATABASE_REQUIRED : 00000100
LOGGER_ACTIVATED : 00001000
DEBUG : 00010000
FLOAT_SUPPORT : 00100000
RECOVERY_MODE : 01000000
REBOOT_ON_FAILURE : 10000000
We are left shifting the byte for each new constant by an iota. Remember that iota’s value increases when there is a new constant declared. The code listing above is equivalent to :
\n// enum-iota-bitmasks/flags/main.go \n\nconst (\n VERBOSE MyConf = 1 << 0\n CONFIG_FROM_DISK MyConf = 1 << 1\n DATABASE_REQUIRED MyConf = 1 << 2\n LOGGER_ACTIVATED MyConf = 1 << 3\n DEBUG MyConf = 1 << 4\n FLOAT_SUPPORT MyConf = 1 << 5\n RECOVERY_MODE MyConf = 1 << 6\n REBOOT_ON_FAILURE MyConf = 1 << 7\n)
\n\nNow it’s time to use our configuration enum. The function that we will use is the following one :
\n// enum-iota-bitmasks/flags/main.go \n\nfunc MyComplexFunction(conf MyConf, databaseDsn string) {\n //...\n}
\nThe configuration is now just a single argument of type MyConf (which is an uint8 behind).
\nWhen we call our function, we just have to provide the required flags :
\nMyComplexFunction(VERBOSE|REBOOT_ON_FAILURE, "mysql...")
\nHere we tell our function that we want to activate the verbose mode and reboot on failure. The value of VERBOSE|REBOOT_ON_FAILURE is equal to 10000001. Only two bits are set to 1, the first (VERBOSE) and the last (REBOOT_ON_FAILURE).
\n\nTo check if a flag is set, we can use the AND bitwise operation. For instance, if we want to check if the bit corresponding to REBOOT_ON_FAILURE is set in confthe following test can be used :
\nconf & REBOOT_ON_FAILURE != 0
\nIf the previous expression is true, it means that the flag is set.
\nLet’s take an example :
\n// enum-iota-bitmasks/flags/main.go \n\nfunc main(){\n MyComplexFunction(VERBOSE|REBOOT_ON_FAILURE, "test")\n}\nfunc MyComplexFunction(conf MyConf, databaseDsn string) {\n fmt.Printf("conf : %08b\\n", conf)\n test := conf & REBOOT_ON_FAILURE\n fmt.Printf("test : %08b\\n", test)\n}
\nThe execution of the previous program will output :
\nconf : 10000001\ntest : 10000000
\nThe variable test is different of 0 (00000000 as a byte). It seems to work. Let’s take another example :
\ntest2 := conf & CONFIG_FROM_DISK
\nis equal to 00000000. The flag is not set.
\n\nThe operation to use here is the XOR. For instance, imagine that you have a config variable set, but you wish to activate a deactivated flag (or deactivate an activated flag) :
\n// toogle FLOAT_SUPPORT => Activate\nconf = conf ^ FLOAT_SUPPORT\ntest = conf & FLOAT_SUPPORT\nfmt.Printf("test : %08b\\n", test)
\nThe previous snippet will output :
\ntest 00100000
\nThe flag has been toggled; the test says that it is now activated.
\n\nTo clear a flag, the bitwise operation to use is AND NOT. Here is an example where FLOAT_SUPPORT has been previously activated :
\nconf = conf &^ FLOAT_SUPPORT\ntest = conf & FLOAT_SUPPORT\nfmt.Printf("test : %08b\\n", test)
\nWill output 00000000, which means that the flag has been cleared from conf.
\n\n//snippet 1\ntype Elem uint8\n\nconst (\n OptB Elem = iota + 5\n OptA\n OptC\n)
\nTrue or False. Go version 1 supports enum types.
What is the value of OptA
in snippet 1 (see below)?
What are the binary operators for AND, OR, XOR, NOT, AND NOT?
What is the type of OptC
?
What is the operator to shift to the left a binary digit?
True or False. Go version 1 supports enum types.
\nFalse
When you want to create an enum type, you will need to write additional methods
What is the value of OptA
in snippet 1 (see below)?
What are the binary operators for AND, OR, XOR, NOT, AND NOT?
\nAND : &
OR : |
XOR : ^
NOT : ^
AND NOT : & ^
What is the type of OptC
?
Elem
What is the operator to shift to the left a binary digit ?
\n>>
An enumeration type (or enum) is a data type that is composed of a set of values explicitly defined
\nEx: The days of the week
Ex: The months of the year
Ex: The statuses of an e-commerce order (Created, Locked, Paid, Shipped, ...)
Go version 1 does not support enumeration types out of the box
However, you can follow this recipe to create an enum E
Create a new type E
.
The underlying type of E
is int
Define a set of constants of type E
The first constant has the value 0
, the second 1
, .…
Each constant is an enum element. They need to be named accordingly.
E
needs to implement the following interfaces json.Marshaler, json.Unmarshaler, fmt.Stringer
(if you use JSON, if you use another marshaling format in your application, you will need to implement the required interfaces).
You can use a library to generate those implementations for you.
iota
is a predeclared identifier. It represents successive untyped integer constants.
We can use it in a “grouped” declaration of constants.
Its value starts at zero and is equal to the index of the constant.
A bit is a binary digit (0 or 1)
A byte is 8 bits.
Go has bitwise logical and shift operators. We can use them only with integers
\n&
: AND
|
:OR
^
:XOR
&^
:AND NOT
<<
:left shift
>>
:right shift
An uint8
is stored in memory with 8 bits
You can use an uint8
to pass a set of 8 boolean arguments to a function or method
Previous
\n\t\t\t\t\t\t\t\t\tBasic HTTP Server
\n\t\t\t\t\t\t\t\tNext
\n\t\t\t\t\t\t\t\t\tDates and time
\n\t\t\t\t\t\t\t\t