🍺 Go Programming Language
In-depth internals, my personal notes, example codes and projects. Includes - Thousands of codes, OOP, Concurrency, Parallelism, Goroutines, Mutexes & Wait Groups, Testing in Go, Go tool chain, Backend web development, Some projects including Log file parser using bufio.Scanner, Spam Masker, Retro led clock, Console animations, Dictionary programs, Social Network built using Go and GopherJS, Database Connectivity and working (MySQL, MongoDB, Redis), GopherJS and lot more..
Author
Aditya Hajare (Linkedin).
Current Status
WIP (Work In Progress)!
License
Open-sourced software licensed under the MIT license.
Important Notes
Go Configurations
+ Environment Configurations
- Open up
.profile
or .zshrc
or .bashrc
depending on our OS and add/edit following:
#!/bin/bash
# Specifies where the Go destribution is installed on the system.
export GOROOT=/usr/local/go
# Specifies top-level directory containing source code for all our Go projects.
# Inside this directory, we need to create 3 more directories viz. "src", "pkg" and "bin".
export GOPATH=~/adiwork/go # This directory is also known as Go Workspace.
# "src" directory inside Workspace represents where all the Go source code will be stored.
# "pkg" directory inside Workspace represents where the compiled Go packages will be stored.
# "bin" directory inside Workspace represents where the produced Go compiled binaries will be stored.
# Specifies where Go should install compiled binaries.
export GOBIN=${GOPATH}/bin
# Attaching GOROOT and GOBIN to shell environment's path variable.
export PATH=${PATH}:/usr/local/bin:${GOROOT}/bin:${GOBIN}
- Execute following command to get
stringer
:
go get -u golang.org/x/tools/cmd/stringer
+ VS Code Configurations
- My VS Code configs for Go:
{
"go.lintTool": "golangci-lint",
"go.formatTool": "goimports",
"go.useLanguageServer": true,
"go.lintOnSave": "package",
"go.vetOnSave": "package",
"go.vetFlags": [
"-all",
"-shadow"
]
}
Basics
- Go is a
strongly typed
language. Because of that, it helps Go compiler to identify many types of errors at compile time
even before our program is run.
+ Packages
- All package files, should be in the same (single) directory. i.e. all package source code files should be located in a one single directory.
- All files in a specific folder should belong to a one single package. It's a convention, not a rule.
There are 2 kinds of packages in Go: Executable Packages
and Library Packages
.
- Executable Packages
- It's name should always be
package main
.
Executable Package
should also contain main()
function and that too only once.
- These are created only for
running
it as a Go program.
- These cannot be imported into a Go program.
- Package name should be
main
.
- Library Packages
- Almost all
Go Standard Library Packages
are of type Library Packages
.
- They are reusable packages.
- They are not executable packages. So we can't run them.
- We can only
import
them.
- These are created only for
reusability
purposes.
- Package name can have any name.
- Doesn't need to have function named
main()
. To avoid confusion, it's better not to have function named main()
in a reusable package.
+ Function init()
- The
init()
function is used to initialize
the state of a package
.
- Go automatically calls
init()
function before
calling command-line
package's main()
function.
+ Scopes
- Same name cannot be declared again inside a same scope.
- There are following types of scopes in Go:
package
: Each Go package has it's own scope
. For e.g. declared funcs
are only visible
to the files belonging to same package
.
file
: Imported packages are only visible to the importing file. Each file has to import external packages on it's own.
func
.
block
.
+ Renaming Imports
+ Exporting
+ Data Types
literal
means the value
itself. Unline variable
, a literal
doesn't have a name.
- There are following data types in Go:
- Basic type: Numbers, strings, and booleans come under this category.
- Aggregate type: Array and structs come under this category.
- Reference type: Pointers, slices, maps, functions, and channels come under this * category.
- Interface type
- Basic Data Types
- Following are the basic data types in Go:
- Numeric:
// Integer Types
uint8 // Unsigned 8-bit integers (0 to 255)
uint16 // Unsigned 16-bit integers (0 to 65535)
uint32 // Unsigned 32-bit integers (0 to 4294967295)
uint64 // Unsigned 64-bit integers (0 to 18446744073709551615)
int8 // Signed 8-bit integers (-128 to 127)
int16 // Signed 16-bit integers (-32768 to 32767)
int32 // Signed 32-bit integers (-2147483648 to 2147483647)
int64 // Signed 64-bit integers (-9223372036854775808 to 9223372036854775807)
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// Floating Types
float32 // IEEE-754 32-bit floating-point numbers
float64 // IEEE-754 64-bit floating-point numbers
complex64 // Complex numbers with float32 real and imaginary parts
complex128 // Complex numbers with float64 real and imaginary parts
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// Other Numeric Types
byte // same as uint8
rune // same as int32
uint // 32 or 64 bits
int // same size as uint
uintptr // an unsigned integer to store the uninterpreted bits of a pointer value
- Boolean:
bool // Represents 'true' or 'false'
- String:
- In Go language, strings are different from other languages like Java, C++, Python, etc.
- Strings can't be
null
in Go.
- It is a sequence of variable-width characters where each and every character is represented by one or more bytes using UTF-8 Encoding.
- In Go, a
string
is in effect is a read-only slice of bytes (immutable).
- Or in other words, strings are the immutable chain of arbitrary bytes (including bytes with zero value) and the bytes of the strings can be represented in the Unicode text using UTF-8 encoding.
- String literals can be created in 2 ways:
- Using double quotes
- Using backticks
+ Variables
- Variables in Go Lang
- In Go, we have to declare a variable before we can use it. This is required and necessary for the
compile time safety
.
- Variables are not created at
compile time
. They are created at run time
.
- The unnamed variables are
pointers
(like in C).
- Once we declare a type for a variable, it cannot be changed later. It is static.
- Zero Values
- Unused variables
- Unused variables in
blocked scope
are not allowed in Go since they cause maintenance nightmares
. If we declare a variable in blocked scope
then we must use it or else completely remove it from the block. We cannot have unused variables declared in blocked scope
dangling in our source codes. Go throws unused variable errors at compile time
only.
- We should avoid using
package level
variables. Go doesn't throw unused variable errors
at compile time
for variables declared at package level
.
- Multiple Declarations
- Sometimes it is also called as parallel variable declarations.
- Declaring multiple variables with
different types
in a single statement:
package main
func main() {
var (
adiBool bool
adiInt int
adiFloat float64
adiStr string
adiPointer *string
)
}
- Declaring multiple variables with
same type
in a single statement:
package main
func main() {
var foo, bar, baz int
}
- Type Inference
- Short Declaration
- Multiple Short Declarations
- We can declare and initialize
multiple variables
of different types
using short declaration
syntax:
package main
main() {
someFlag, age, name := true, 30, "आदित्य" // Multiple variables of different types.
}
- In this type of declaration, number of values and number of names must be the same. Otherwise it will result in error.
- Redeclarations With Short Declarations
Short Declaration
can initialize new variables and assign to existing variables at the same time.
- At least one of the variable in
Short Declaration Redeclaration
must be a new variable.
- For e.g.
package main
main() {
var someFlag bool
// someFlag := true // Error! At least one variable must be new to make this work.
someFlag, age := true, 30 // This works! Because 'age' is a new variable being declared in the same statement. someFlag will be set (redeclared) to true.
}
+ Blank Identifier
“There are only two hard things in Computer Science: cache invalidation and naming things”. Tim Bray quoting Phil Karlton
- Go doesn't allow
unused variables
in blocked scope
.
- To ignore a variable,
Blank Identifier (_)
is used as a variable name in Go.
- Go compiler will not throw unsed variable error if a blocked scope variable is named
_
.
- We cannot use value assigned to
_
.
- It is like a black hole that swallows variable.
- Detailed information and usage of Blank Identifier
+ fmt.Printf and fmt.Sprintf Formatting
- Following formatting can be used with
fmt.Printf
as well as fmt.Sprintf
:
// String and slice of bytes
%s // the uninterpreted bytes of the string or slice
%q // a double-quoted string safely escaped with Go syntax
%x // base 16, lower-case, two characters per byte
%X // base 16, upper-case, two characters per byte
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// Boolean
%t // the word true or false
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// General
%v // The value in a default format. When printing structs, the plus flag (%+v) adds field names.
%#v // a Go-syntax representation of the value
%T // a Go-syntax representation of the type of the value
%% // a literal percent sign; consumes no value
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// Integer
%b // base 2
%c // the character represented by the corresponding Unicode code point
%d // base 10
%o // base 8
%q // a single-quoted character literal safely escaped with Go syntax
%x // base 16, with lower-case letters for a-f
%X // base 16, with upper-case letters for A-F
%U // Unicode format: U+1234; same as "U+%04X"
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// The default format for %v
bool // %t
int, int8 // %d
uint, uint8 // %d, %x if printed with %#v
float32, complex64 // %g
string // %s
chan // %p
pointer // %p
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// Floating-point and complex constituents
%b // decimalless scientific notation with exponent a power of two, in the manner of strconv.FormatFloat with the 'b' format, e.g. -123456p-78
%e // scientific notation, e.g. -1.234456e+78
%E // scientific notation, e.g. -1.234456E+78
%f // decimal point but no exponent, e.g. 123.456
%F // synonym for %f
%g // %e for large exponents, %f otherwise
%G // %E for large exponents, %F otherwise
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// Floating-point Precision
%f // default width, default precision
%9f // width 9, default precision
%.2f // default width, precision 2
%9.2f // width 9, precision 2
%9.f // width 9, precision 0
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// Pointer
%p // base 16 notation, with leading 0x
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// Other flags
+ // always print a sign for numeric values; guarantee ASCII-only output for %q (%+q).
- // pad with spaces on the right rather than the left (left-justify the field).
# // alternate format: add leading 0 for octal (%#o), 0x for hex (%#x); 0X for hex (%#X); suppress 0x for %p (%#p); for %q, print a raw (backquoted) string if strconv.CanBackquote returns true;
' ' (space) // leave a space for elided sign in numbers (% d); put spaces between bytes printing strings or slices in hex (% x, % X).
0 // pad with leading zeros rather than spaces; for numbers, this moves the padding after the sign.
+ Slice Vs. Array - Performance
Slice
operations are cheap!
Slicing
: Creates a new slice header
.
Assigning a Slice to another Slice
or, passing it to a function
: Only copies the slice header
.
Slice header has a fixed size
and it doesn't change
even if we have got millions of elements.
Array
can be expensive as compared to Slice
.
Assigning an array to another array
or passing it to a function
: Copies all the elements of it.
+ Composite Types In Go
- Following are the
Composite Types
in Go:
- Arrays: Collection of elements.
Indexable
and Fixed Length
.
- Slices: Collection of elements.
Indexable
and Dynamic Length
.
- Strings: Byte Slices. ASCII and UNICODE.
- Maps: Collection of
Indexable Key-Value Pairs
.
- Structs: Groups different types of variables together.
Type System In Go
- At
compile time
, a Go compiler
can catch overflow errors
.
- In runtime, when
overflows
occurs:
integer
wrap arounds and go to their minimum and maximum values.
float
wrap arounds to positive infinity
or negative infinity
.
+ Important Links
+ Predeclared Types
- A
predeclared type
is a built-in type
that we can use everywhere without importing
any package
.
- A
built-in type
means it's a core feature of Go
i.e. it comes with compiler
itself.
- A
predeclared type
has a name and we can use it in any scope
.
- We don't have to
declare
a predeclared type
before using it.
- It has a
type representation
i.e. how Go see it and how we can use it. In other words, what values a type can represent
.
- It has a
size in bytes
i.e. how much space it needs in memory and it also determines the range of values it can represent.
- Go cannot catch
overflow errors
in runtime
. For e.g. A variable
belongs to runtime
and it's value cannot be known at the compile time
.
- In Go, when variable values
overflow
, they gets wrapped around i.e. They get reassigned to the minimum value their variable type
can represent.
- Examples of
Predeclared Types
:
bool // 'bool' is a predeclared type and it has following characteristics:
// Name: bool
// Representation: 'true' or 'false'
// Size: 1 byte
int // 'int' is a predeclared type and it has following characteristics:
// Name: int
// Representation: -1, 0, 1, 1000000000000000
// Size: 8 byte
+ Defined Types
+ Aliased Types
byte
and uint8
are exactly the same types
just with diferent names
.
rune
and int32
are exactly the same types
just with diferent names
. i.e. rune
is an alias
of int32
. The rune
type is used to represent unicode characters
.
Type Alias
declaration is not for everyday usage. It is mainly used in very huge codebase refactors.
Constants
+ Important Links
+ Constant Types
+ Multiple Constants Declaration
Constants
get their types
and expressions
from the previous constant
.
- We can declare
multiple constants
in a single go as below:
func main() {
// Multiple constants of same type in one go
const min, max int = 1, 1000
// Declaring in group
const (
min int = 1
max int = 1000
)
// Constants get their types and expressions from the previous constant
const (
min int = 1000 // 1000
max // 1000
)
}
+ Typeless Or Untyped Constants
+ Default Types
Conversion
only happens when a type is needed
.
- Go
converts
a typeless constant
to a typed
value when a type
is needed.
- For e.g.
func main() {
const min int32 = 1000
max := 5 + min // Type of 'max' is 'int32'
// Internally this happens: max := int32(5) + min
}
- Go
implicitly converts
the typeless constant
to a typed value
.
- For e.g.
func main() {
const min = 1000
max := 5 + min // Type of 'max' is 'int'
// Internally this happens: max := int(5) + int(min)
}
- An
untyped constant
has a default type
.
- Go
evaluates the expression
then it converts
the resulting typeless value
to its default value
.
+ IOTA
+ Common Abbreviations Used In Go
Error Handling
+ nil
Strings Runes And Bytes
+ Important Links
+ Strings Runes And Bytes 101
- A
string value
is nothing but a series of bytes
.
- We can represent a
string value
as a byte slice
. For e.g.
"hey" // String value
[]byte{104, 101, 121} // Representing string "hey" in byte slice
[]byte("hey") // Converting string "hey" into byte slice
string([]byte{104, 101, 121}) // Converting byte slice into string value
- Instead of
numbers
(byte slice), we can also represent string characters
as rune literals
.
Numbers
and Rune Literals
are the same thing.
- In Go,
Unicode Code Points
are called Runes
.
- A
Rune literal
is a typeless integer literal
.
- A
Rune literal
can be of any integer type
. for e.g. byte (uint8)
, rune (int32)
or any other integer type
.
- In short,
Rune
is a Unicode Code Point
that is represented by an Integer Value
.
- Using
UTF-8
we can represent Unicode Code Points
between 1 byte
and 4 bytes
.
- We can represent any
Unicode Code Point
using the Rune Type
because it can store 4 bytes
of data. For e.g.
char := '🍺'
String values
are read-only byte slices
i.e.
string value ----> read-only []byte
String to Byte Slice
conversion creates a new []byte slice
and copies the bytes of the string to a new slice's backing array
. They don't share the same backing array
.
- In short,
String
is an immutable byte slice
and we cannot change any of it's elements. However, we can convert string to a byte slice
and then we can change that new slice
.
- A
string
is a data structure that points to a read-only backing array
.
UTF-8
is a variable length encoding
(for efficiency). So each rune
may start at a different index
.
for range
loop jumps over the runes of a string
, rather than the bytes of a string
. Each index
returns the starting index
of the next rune
.
Runes
in a UTF-8 encoded string
can have a different number of bytes
because UTF-8
is a variable byte-length encoding
.
- Especially in scripting languages, we can manipulate
UTF-8 strings
by indexes
easily. However, Go doesn't allow us to do so by default
because of efficiency reasons.
- Go never hides the cost of doing something.
[]rune(string)
creates a new slice
, and copies each rune
to new slice's backing array
. This is inefficient way of indexing strings.
- A
string
value usually use UTF-8
so it can be more efficient because each rune
on the other hand uses 1 to 4 bytes
(variable-byte length).
- Each
rune
in []rune
(Rune Slice) has the same length i.e. 4 bytes
. It is inefficient because the rune
type is an alias to int32
.
- In Go, if our
source code file
is encoded into utf-8
then String Literals
in our file are automatically encoded into utf-8
.
- When we're working with
bytes
, continue working with bytes
. Do not convert a string
to []byte
(Byte Slice) or vice versa, unless necessary. Prefer working with []byte
(Byte Slice) whenever possible. Bytes
are more efficient and used almost everywhere in Go standard libraries.
Maps In Go
+ Maps 101
Maps
allows us to quickly access to an element/value
using a unique key
.
Map keys
must be unique
because otherwise it can't find the corresponding values/elements
.
- The types of
Map Keys
and Values in Maps
can be different
.
- A
Map Key
must be a comparable type
.
- All
Map Keys
and Map Values
must belong
to their corresponding types
. They can't be mixed up
.
- A
Map Variable (or a Value)
is nothing but a pointer
to a Map Header Value
in the memory
.
- A
Map Value
only contains the memory address
of a Map Header
.
Structs In Go
+ Inheritance vs. Composition
+ Structs 101
OOP In Go With Methods And Interfaces
+ Methods
Methods
enhance types
with additional behavior.
Methods
of the type
are called Method Set
.
- To attach method to a
type
:
// Syntax
// "varName Type" is called a "receiver"
func (varName Type) funcName() {
// Code
}
// Example
// "book" is a struct here
func (b book) printBook() {
fmt.Println(b.title, b.price)
}
- A
receiver
is nothing but method's input parameters
written before
a method name
.
- A
method
belongs to a single type
.
Methods
on different types
can have the same names
.
Method Expressions
allows us to call methods
through types
. For e.g.
// "game" is a struct type
game.print(cod)
game.print(battlefield)
- Behind the scenes, a
method
is a function
that takes receiver
as it's first argument
.
+ Pointer Receivers
- We can define
methods
on types
using Pointer Receivers
.
- The only difference between
method
and a function
is that a method
belongs to a type
, whereas a function
belongs to a package
.
- Consistent Design Tip: When one of the
methods
in any type
are using pointer receiver
, it is better to convert all method receivers
of that type
to pointer receivers
.
- We (must) use a
pointer receiver
when we want to make changes to a receiver variable
. In other words, use a pointer receiver
when the received value into method
is going to be very large.
+ Attaching Methods To Any Types
- We can attach methods to any type in Go. For e.g.
// Basic Types
int
string
float64
// Bare Types
array
struct
// -----------------------
// Do not use "Pointer Receivers" with below types since they already carry a pointer with themselves.
// i.e. slice, map, chan, func
// -----------------------
// Pointer Bearing Types
slice
map
chan // Channels
// We can also attach methods to:
func
+ Interfaces
+ Type Assertion
Type Assertion
allows us to extract
the dynamic value
from Interface
.
- It can also be used to check (assert) whether the
Interface Value
provides the method
we want.
+ Empty Interface
+ Type Switch
Concurrency And Parallelism
+ Concurrency
- The composition of independently executing tasks.
- Applied when dealing with handling lots of things at once.
- The focus is on how to structure a solution to solve a problem which may or may not be solved in a parallel manner.
Concurrency
is a way to structure a program by breaking it into pieces that can be executed independently.
- Communication is the means to coordinate the independent executions.
- Go supports concurrency. Go provides:
- concurrent execution (
goroutines
).
- synchronization and messaging (
channels
).
- multi-way concurrent control (
select
).
- IMPORTANT POINTS:
Concurrency
is powerful.
Concurrency
is not parallelism
.
Concurrency
enables parallelism
.
Concurrency
makes parallelism
(and scaling and everything else) easy.
+ Parallelism
Parallelism
is the simultaneous execution of computations.
- Programming as the simultaneous execution of (possibly related) computations.
- It's all about doing lots of things at once.
+ Concurrency vs. parallelism
- “Concurrency is about dealing with lots of things at once. Parallelism is about doing lots of things at once.” — Rob Pike
- Concurrency is a property of a program where two or more tasks can be in progress simultaneously. Parallelism is a run-time property where two or more tasks are being executed simultaneously. Through concurrency you want to define a proper structure to your program. Concurrency can use parallelism for getting its job done but remember parallelism is not the ultimate goal of concurrency.
Concurrency
is about dealing with lots of things at once.
Parallelism
is about doing lots of things at once.
- Not the same, but related.
Concurrency
is about structure
, parallelism
is about execution
.
Concurrency
provides a way to structure a solution
to solve a problem that may (but not necessarily) be parallelizable
.
- An analogy
Concurrent
: Mouse, keyboard, display, and disk drivers.
Parallel
: Vector dot product.
Goroutines
- In Go,
concurrency
is achieved by using Goroutines
.
Goroutines
are functions
or methods
which can run concurrently
with others methods
and functions
.
Goroutines
are lightweight threads
that are managed by the Go runtime
.
- They are very much similar like
threads
in Java
but light weight and cost of creating them is very low.
- When we run a function as a
Goroutine
, we are running the function concurrently
.
- Place the keyword
go
before a function call to execute it as a Goroutine
.
- To run a method or function concurrently prefix it with keyword
go
.
- For e.g.
package main
import (
"fmt"
"time"
)
func print() {
fmt.Println("Printing from goroutine")
}
func main() {
go print()
time.Sleep(1 * time.Second)
fmt.Println("Printing from main")
}
- Program is terminated when
main()
function execution is completed. When the program terminates, all Goroutines
are terminated regardless of the fact if all the Goroutines
has completed execution or not.
- We can also run
Anonymous Functions
as Goroutines
as follows:
// Executing anonymous function as Goroutine
go func() {
//
}()
+ Advantages of Goroutines over Threads
Goroutines
have a faster startup time than threads
.
Goroutines
come with built-in primitives
to communicate safely between themselves called as channels
.
Goroutines
are extremely cheap when compared to threads
. They are only a few kb
in stack size
and the stack
can grow and shrink according to needs of the application whereas in the case of threads
the stack size
has to be specified and is fixed
.
Channels
+ Buffered Channels
Mutexes And Wait Groups From GoSync Package
- Since
Goroutines
run in a same address space
, they have access to shared memory
and this access
must be synchronised
. Go's motto is to share memory
by communicating
(Goroutines
and Channels
makes this possible).
- Sometimes, some problems are better suited to using the
traditional forms of synchronisation
. Go allows us to make use of these Synchonisation Primitives
by using the Sync
package.
+ Mutexes
- A
Race Condition
happens when two or more threads
can access shared data
and try to change that shared data
at the same time. We can use Mutex
to solve this problem.
- A
Mutex
is a Mutual Exclusion Lock
. It's a Synchronisation Primitive
.
- It is used to
protect shared data
which is simultaneously accessed
by multiple treads
.
- Making changes to shared data and reading a shared data (e.g. for printing purposes) are still considered accessing the same data simultaneously.
+ Wait Groups
Wait Groups
are another Synchronisation Primitive
.
- A
Wait Group
basically waits for collection
of Goroutines
to finish execution.
Go Vet
go vet
command helps us catch errors which are not generally caught by Go Compiler.
- For e.g.
- Go to directory:
62-Go-Vet-To-Catch-Errors
- Execute file with below command:
go vet main.go
- It will catch the error where
int
is supplied to fmt.Printf()
whereas string
value should've been supplied. This error isn't caught by Go Compiler since the program is still syntactically correct.
Go Documentation Server On Local Machine