Learning Go: An Idiomatic Approach to Real-World Go Programming
Chapter 1: Setting Up Your Go Environment
Installing the Go Tools
1. Downloading Golang
curl -OL https://golang.org/dl/go{version}.{OS}-{CPU architecture}.tar.gzExample
curl -OL https://golang.org/dl/go1.22.5.linux-amd64.tar.gz2. Create a directory for your Go projects. Example
make ~/go3. Unzip and copy it
tar -C /usr/local -xzf go1.22.5.linux-amd64.tar.gz4. Set environments and apply changes
export PATH=$PATH:/usr/local/go/bin
export GOPATH=$HOME/go
export PATH=$PATH:$GOPATH/bin
source ~/.bashrc # Or whichever file you edited5. Verify installation
The Go Workspace
6. Set up your Go workspace
The go Command
go run and go build
go run and go buildgo run
go rungo build
go build"-o" option means output name
go third-party packages
go third-party packagesTo install source code like tool, for specific version use @ symbol (@latest). Then downloads, compiles and installs into your $GOPATH/bin. To update rerun install command.
go fmt
go fmtAutomatically reformat your code. go fmt in goimports. You can download goimports with this command
Linting and Vetting
Linting and VettingInstalling linter
Run
We can run multiple tools together with golangci-lint
Makefiles
Example of Makefile
Chapter 2: Primitive Types and Declarations
Literals
Go, like most modern languages, assigns a default zero value to any variable that is declared but not assigned a value.
Integer literals:
They are normally base ten, but different prefixes are used to indicate other bases: 0b for binary (base two), 0o for octal (base eight), or 0x for hexadecimal (base sixteen).
To make it easier to read longer integer literals, Go allows you to put underscores in the middle of your literal.
Floating literals
They can also have an exponent specified with the letter e and a positive or negative number (such as 6.03e23)
Rune literals
Unlike many other languages, in Go single quotes and double quotes are not interchangeable
String literals
Double quotes to create an interpreted string literal:
"Greetings and\n\"Salutations\""Raw string literal.
Booleans
Numeric Types
int8
-128 to 127
int16
-32768 to 32767
int32
-2147483648 to 2147483647
int64
-9223372036854775808 to 9223372036854775807
uint80
to 255
uint160
to 65536
uint320
to 4294967295
uint640
to 18446744073709551615
A
byteis an alias foruint8;On a 32-bit CPU, int is a 32-bit signed integer like an int32. On most 64-bit CPUs, int is a 64-bit signed integer, just like an int6
uint. It follows the same rules as int, only it is unsigned (the values are always 0 or positive).
float32
3.40282346638528859811704183484516925440e+38
1.401298464324817070923729583289916131280e-45
float64
1.797693134862315708145274237317043567981e+308
4.940656458412465441765687928682213723651e-324
Dividing a nonzero floating point variable by 0 returns +Inf or -Inf (positive or negative infinity), depending on the sign of the number Dividing a floating point variable set to 0 by 0 returns NaN (Not a Number).
Go defines two complex number types. complex64 uses float32 values to represent the real and imaginary part, and complex128 uses float64 values
A Taste of Strings and Runes
Strings in Go are immutable; you can reassign the value of a string variable, but you cannot change the value of the string that is assigned to it.
The rune type is an alias for the int32 type, just like byte is an alias for uint8.
Explicit Type Conversion
Go doesn't allow automatic type promotion between vari ables. You must use a type conversion when variable types do not match. Even different-sized integers and floats must be converted to the same type to interact.
var Versus :=
:=var keyword to declare
Short declaration
The := operator can do one trick that you cannot do with var: it allows you to assign values to existing variables, too.
const
constThey can only hold values that the compiler can figure out at compile time. This means that they can be assigned:
Numeric literals
true and false
Strings
Runes
The built-in functions complex, real, imag, len, and cap
Expressions that consist of operators and the preceding values
Go doesn't provide a way to specify that a value calculated at runtime is immutable.
Typed and Untyped Constants
Constants can be typed or untyped.
Unused variables
Go requirement is that every declared local variable must be read. It is a compile-time error to declare a local variable and to not read its value.
Perhaps surprisingly, the Go compiler allows you to create unread constants with const.
Naming Variables and Constants
Idiomatic Go doesn't use snake case (names like index_counter or number_tries). Instead, Go uses camel case (names like indexCounter or numberTries) when an identifier name consists of multiple words.
Chapter 3: Composite Types
Arrays - Too rigid to use Directly
Declared to be [3]int a different type from an array that’s declared to be [4]int. This also means that you cannot use a variable to specify the size of an array, because types must be resolved at compile time, not at runtime.
What’s more, you can’t use a type conversion to convert arrays of different sizes to identical types. Because you can’t convert arrays of different sizes into each other, you can’t write a function that works with arrays of any size and you can’t assign arrays of different sizes to the same variable.
Slices
Holds a sequence of values, a slice is what you should use. What makes slices so useful is that the length is not part of the type for a slice. This removes the limitations of arrays. We can write a single function that processes slices of any size
Using […] makes an array. Using [] makes a slice
var x []int
This creates a slice of ints. Since no value is assigned, x is assigned the zero value for a slice, which is something we haven’t seen before: nil
A nil slice contains nothing.
A slice is the first type we’ve seen that isn’t comparable. The only thing you can compare a slice with is nil:
len
When you pass a nil slice to len, it returns 0.
append
Every time you pass a parameter to a function, Go makes a copy of the value that’s passed in. Passing a slice to the append function actually passes a copy of the slice to the function. The function adds the values to the copy of the slice and returns the copy. You then assign the returned slice back to the variable in the calling function.
Capacity
Every slice has a capacity, which is the number of consecutive memory locations reserved. Each value added increases the length by one. When the length reaches the capacity, there’s no more room to put values. If you try to add additional values when the length equals the capacity, the append function uses the Go runtime to allocate a new slice with a larger capacity. The values in the original slice are copied to the new slice, the new values are added to the end, and the new slice is returned.
The Go runtime provides services like memory allocation and garbage collection, concurrency support, networking, and implementations of built-in types and functions.
The rules as of Go 1.14 are to double the size of the slice when the capacity is less than 1,024 and then grow by at least 25% afterward. Just as the built-in len function returns the current length of a slice, the built-in cap function returns the current capacity of a slice.
make
make function allows us to specify the type, length, and, optionally, the capacity.
We can also specify an initial capacity with make:
You can also create a slice with zero length, but a capacity that’s greater than zero:
Declaring your slice
The primary goal is to minimize the number of times the slice needs to grow.
You can create a slice using an empty slice literal:
Slicing Slices
A slice expression creates a slice from a slice.
Slices share storage sometimes. When you take a slice from a slice, you are not making a copy of the data.
Changing x modified both y and z, while changes to y and z modified x. Slicing slices gets extra confusing when combined with append.
The full slice expression protects against appen
copy
If you need to create a slice that’s independent of the original, use the built-in copy function.
The copy function takes two parameters. The first is the destination slice and the second is the source slice.
The capacity of x and y doesn’t matter; it’s the length that’s important.
You can use copy with arrays by taking a slice of the array. You can make the array either the source or the destination of the copy
Strings and Runes and Bytes
Go uses a sequence of bytes to represent a string.
Go source code is always written in UTF-8. Unless you use hexadecimal escapes in a string literal, your string literals are written in UTF-8.
This code (len(s)) prints out 10, not 7, because it takes four bytes to represent the sun with smiling face emoji in UTF-8.
Even though Go allows you to use slicing and indexing syntax with strings, you should only use it when you know that your string only contains characters that take up one byte.
A single rune or byte can be converted to a string:
A string can be converted back and forth to a slice of bytes or a slice of runes.
Maps
In this case, nilMap is declared to be a map with string keys and int values. The zero value for a map is nil. A nil map has a length of 0. Attempting to read a nil map always returns the zero value for the map’s value type. However, attempting to write to a nil map variable causes a panic.
We can use a := declaration to create a map variable by assigning it a map literal:
Maps automatically grow as you add key-value pairs to them.
If you know how many key-value pairs you plan to insert into a map, you can use make to create a map with a specific initial size.
Passing a map to the len function tells you the number of key-value pairs in a map.
The zero value for a map is nil.
Maps are not comparable. You can check if they are equal to nil, but you cannot check if two maps have identical keys and values using == or differ using !=.
The key for a map can be any comparable type. This means you cannot use a slice or a map as the key for a map.
Use a map when the order of elements doesn’t matter. Use a slice when the order of elements is important.
What is a Hash Map?
A hash map does fast lookups of values based on a key. Internally, it’s implemented as an array. When you insert a key and value, the key is turned into a number using a hash algorithm. These numbers are not unique for each key. The hash algorithm can turn different keys into the same number. That number is then used as an index into the array. Each element in that array is called a bucket. The key-value pair is then stored in the bucket. If there is already an identical key in the bucket, the previous value is replaced with the new value. Each bucket is also an array; it can hold more than one value. When two keys map to the same bucket, that’s called a collision, and the keys and values for both are stored in the bucket.
Reading and Writing a Map
The comma ok Idiom
Deleting from Maps
If the key isn’t present in the map or if the map is nil, nothing happens. The delete function doesn’t return a value.
Using Maps as Sets
Some people prefer to use struct{} for the value when a map is being used to implement a set. The advantage is that an empty struct uses zero bytes, while a boolean uses one byte. The disadvantage is that using a struct{} makes your code more clumsy. You have a less obvious assignment, and you need to use the comma ok idiom to check if a value is in the set.
Last updated
Was this helpful?