Go Programming Notes
Go Programming Notes
Go Runtime
The Go runtime is a core part of any Go program. It's a library written in Go and C that is included
with every compiled executable. Its responsibilities are crucial for Go's performance and concurrency
model:
• Garbage Collection (GC): It manages memory automatically, tracking and reclaiming memory
that is no longer being used. This prevents memory leaks and simplifies programming.
• Stack Management: It manages the growth and shrinking of goroutine stacks. Go stacks start
small (a few KB) and can grow or shrink dynamically as needed.
• Interface Dispatch: It handles the dynamic method lookups for Go's interfaces.
Go Compilation
Go is a compiled language, meaning source code must be translated into machine code before it can
be executed.
• Fast Compilation: Go's compiler is famously fast, achieved through design choices like no
circular dependencies in packages and simplified header files.
• Static Linking: By default, the Go compiler produces a statically linked executable. This
means the resulting binary includes all the necessary code, including the Go runtime, making
the executables large but highly portable (they don't rely on specific shared libraries being
present on the target system).
Keywords
Keywords are reserved words that have a predefined meaning in the Go language. They cannot be
used as identifiers (names for variables, functions, etc.). Go has only 25 keywords, making the
language relatively small and easy to learn.
Go Keywords
Identifiers
Identifiers are names used to identify variables, functions, types, labels, and packages.
• Rules: Must begin with a letter or underscore (_). Subsequent characters can be letters,
digits, or underscores. They are case-sensitive.
o An identifier starting with a capital letter (e.g., Name, CalculateTotal) is exported and
visible/accessible from outside its package.
Constants
Constants are fixed values that cannot be changed during the execution of a program.
Go
const Pi = 3.14159
• Typed vs. Untyped: Constants can be typed (like Greeting) or untyped (like Pi). Untyped
constants are more flexible and can be used where different numeric types are expected.
• iota: A special constant generator used to create a sequence of related constants. It starts at
0 and increments by 1 for each new const specification in a block.
Go
const (
A = iota // 0
B // 1 (implicitly B = iota)
C // 2 (implicitly C = iota)
Variables
Variables are storage locations that hold values of a specific type. Their values can be changed.
Go
• Type Inference: Go can often infer the type from the initial value:
Go
• Short Variable Declaration (:=): The most common way to declare and initialize variables
inside a function. It cannot be used for package-level variables.
Go
• Multiple Declaration:
Go
var x, y int = 1, 2
a, b := "first", true
Operators
Operators are special symbols or keywords that perform operations on one or more operands.
• Arithmetic: +, -, *, /, % (modulus).
• Comparison (Relational): ==, !=, >, <, >=, <= (Result is a boolean).
• Bitwise: & (AND), | (OR), ^ (XOR), &^ (Bit Clear), << (Left Shift), >> (Right Shift).
Expressions
Examples:
• calculateArea(length, width) (Function call expression, evaluates to the return value of the
function)
• Standard Assignment: The value on the right-hand side is computed and stored in the
variable on the left.
Go
price = 19.99
isReady = true
• Shorthand for Increment/Decrement (Go does not have ++x or x-- as expressions):
Go
i++ // Equivalent to i = i + 1
j-- // Equivalent to j = j - 1
Note: These are statements, not expressions, and cannot be used inside other expressions.
Go
2. Handling multiple return values (e.g., from functions or maps, often including an
error value):
Go
result, err := someFunction() // result gets the value, err gets the error (or nil)
Booleans
• Type: bool
• Usage: Essential for control flow (if, for) and logical operations.
Go
Numeric Types
int16, uint16 16
int64, uint64 64
complex64,
Complex numbers
complex128
uintptr Integer large enough to hold the bit pattern of any pointer
Characters (rune)
Go
Go
x := 42
• Dereference Operator (*): Used to access (read or modify) the value stored at the memory
address the pointer holds.
Go
Go
• Go Pointers vs. C Pointers: Go pointers are safer. You cannot perform pointer arithmetic (like
p++) in Go.
1.8 Strings
• Immutable: Once a string is created, its content cannot be changed. Any operation that
seems to modify a string (like concatenation) actually creates a new string.
• UTF-8 Encoded: Go strings are a sequence of bytes that typically hold UTF-8 encoded text.
• Length: The built-in len() function returns the number of bytes in the string, not the number
of characters (runes).
• Accessing Characters/Bytes:
o Iterating over a string using a for range loop iterates over runes (Unicode characters).
Go
s := "Hello, Go"
if-else Statement
Executes a block of code only if a specified condition is true. Go's if statements do not require
parentheses around the condition.
• Basic:
Go
if score >= 90 {
// ...
} else {
// ...
• Short Statement: An optional short statement can precede the condition. Variables declared
here are scoped only to the if and else blocks.
Go
// Handle error...
} else {
// Use result...
switch Statement
• Implicit break: Unlike C/C++, Go's switch automatically executes a break after a matching
case.
Go
t := [Link]().Hour()
[Link]("Morning")
[Link]("Afternoon")
default:
[Link]("Evening")
for Loop
The for loop is the only looping construct in Go (no while or do-while).
// ...
Go
sum := 1
sum += sum
• Infinite Loop:
Go
// ...
The for range form is used to iterate over elements of collections (arrays, slices, maps, strings, and
channels). It returns two values on each iteration:
String Starting byte index of the rune The rune (Unicode code point)
Go
// ...
break Statement
The break statement immediately terminates the innermost for, switch, or select statement it is
contained in.
• In switch: (Usually redundant due to implicit break, but can be used to exit early before a
case completes.)
continue Statement
The continue statement skips the rest of the current iteration of the innermost for loop and proceeds
to the next iteration.
Go
if i%2 != 0 {
continue // Skip odd numbers (the rest of the loop body is skipped)
if i == 8 {
[Link](i) // Prints: 0, 2, 4, 6
A break statement can be followed by a label to terminate an outer loop when working with nested
loops.
Go
if i == 1 && j == 1 {
break OuterLoop // Jumps out of BOTH loops to the line after the OuterLoop
[Link](i, j)
Functions in Go are defined using the func keyword. They can accept input values (parameters) and
produce output values (return values).
Parameters (Inputs)
Parameters are variables defined in the function signature that receive values when the function is
called.
• Syntax:
Go
• Concise Syntax (Same Type): If consecutive parameters share the same type, you only need
to specify the type once for the last parameter.
Go
return x + y
Go functions can return zero, one, or multiple values. Returning multiple values is one of Go's key
features, commonly used to return a result and an error (value, err).
Go
Go is strictly a "Call by Value" language. When a function is called, the argument values are copied
into the function's parameters.
Call by Value
When passing a variable of a value type (like int, float, bool, string, structs, arrays), a copy of the
variable's value is made and passed to the function. Any modification to the parameter inside the
function does not affect the original variable outside the function.
Go
x := 5
changeValue(x)
// x is still 5
While Go is call-by-value, you can achieve "call by reference" behavior by passing a pointer (the
memory address) to the function.
• However, the function can use the dereference operator (*) on the pointer to access and
modify the original data at that address.
Go
y := 5
// y is now 15
This method is used when you need to modify large data structures (like slices or maps—though they
already behave like reference types—or custom structs) efficiently without copying the entire data.
Go allows you to name the return variables in the function signature. This feature is often referred to
as a "naked return."
• Benefit: Named return variables are initialized to their zero values (0 for int, "" for string,
etc.) and are treated like regular local variables within the function body.
• Usage: If you use a plain return statement (without arguments), the current values of the
named return variables are returned.
Go
func calculate(a, b int) (sum int, diff int) { // sum and diff are named return variables
sum = a + b
diff = a - b
return
Note: While convenient for short functions, use naked returns sparingly in long functions, as they can
reduce clarity.
The blank identifier, represented by the underscore symbol (_), is a special identifier used to
intentionally discard a value.
• Avoiding Compile Errors: Go is strict about unused variables. The blank identifier is used to
accept a value without declaring a new variable for it, thus avoiding the "declared but not
used" compilation error.
Go
[Link](item)
• Imports: The blank identifier can also be used with import to initialize a package for its side
effects (like database drivers) without using any of its exported functions.
Go
import _ "[Link]/go-sql-driver/mysql"
A function can be defined to take a variable number of arguments, known as variadic functions.
• Syntax: The last parameter is preceded by three dots (...). This parameter becomes a slice
inside the function body.
Go
func sumAll(nums ...int) int { // nums is treated as a slice of ints inside the function
total := 0
total += n
return total
// Calling it:
• Passing Slices: If you already have a slice and want to pass all its elements as arguments, you
can use the same ... syntax at the call site. This is called unfurling a slice.
Go
The defer statement schedules a function call to be executed immediately before the function
containing the defer returns.
• LIFO Stack: Deferred calls are pushed onto a stack. When the surrounding function exits, the
deferred calls are executed in Last-In, First-Out (LIFO) order.
Go
if err != nil {
return
defer [Link]()
[Link]("Reading file...")
Key Point: The arguments to a deferred function are evaluated when the defer statement is
executed, not when the deferred call is executed.
1. Base Case: A condition that stops the recursion to prevent an infinite loop (e.g., if
$n=0$).
2. Recursive Step: The call to the function itself, usually solving a smaller subproblem.
Go
if n == 0 { // Base Case
return 1
// Recursive Step
return n * factorial(n-1)
• Caution: Deep recursion can consume large amounts of memory on the stack and lead to a
stack overflow error, though Go's runtime dynamically managed stacks help mitigate this
better than many other languages.
Functions in Go are first-class citizens, meaning they can be treated just like any other value (like an
int or a string).
1. Assigned to variables.
This is a core concept for functional programming, often used for defining callbacks or strategy
patterns.
• Defining the Function Signature (Type): You need to define the type of the function
parameter.
Go
return x * y
}
// Calling it:
An array is a fixed-size sequence of elements of a single, specific type. The size is part of the array's
type.
• Declaration Syntax:
Go
• Zero Value: When declared without initialization, all elements are set to the zero value of
their type (e.g., 0 for int, "" for string).
Go
Go
firstAge := ages[0] // 20
A multidimensional array is an array whose elements are themselves arrays. They are often used to
represent grids or tables.
Go
var arrayName [rows][cols]type
Go
grid := [3][2]int{
Go
• Pass by Value: A complete copy of the entire array is made and given to the function.
o Implication 1: Changes made to the array parameter inside the function do not
affect the original array.
Go
arr[0] = 99
data := [3]int{1, 2, 3}
modifyArray(data)
Note: Due to the performance cost and fixed size, arrays are less common in Go programming than
slices (see 3.4).
A slice is a dynamic, flexible view or segment of an underlying array. It is the preferred way to work
with lists of data in Go.
3. Capacity: The number of elements in the underlying array, starting from the slice's
pointer, that the slice can include.
1. Slice Literal: Creates an underlying array and returns a slice that references it.
Go
2. make function: Used to create a slice, specifying its length and optional capacity.
Go
3. Slicing an Array/Slice: Creates a new slice header pointing to a part of the original
data.
Go
When a slice is passed to a function, the slice header (Pointer, Length, Capacity) is passed by value
(copied).
• Reference Behavior: Since the slice header copy points to the same underlying array as the
original slice, modifications to the elements of the slice parameter inside the function will
affect the original slice.
Go
data := []int{1, 2, 3}
modifySlice(data)
Go
• Creation: You initialize the outer slice, and then initialize each inner slice separately.
Go
matrix := make([][]int, 3)
matrix[0] = []int{1, 2}
matrix[1] = []int{3, 4, 5}
matrix[2] = []int{6}
// Accessing elements
val := matrix[1][2] // 5
A structure (struct) is a user-defined composite type that groups together fields (variables) of
different types into a single unit.
Go
FirstName string
LastName string
Age int
Go
p1 := Person{
FirstName: "Alice",
LastName: "Smith",
Age: 30,
Go
3. Zero Value:
Go
Go
[Link]([Link]) // Alice
Structure Parameters
• Pass by Value: A complete copy of the entire structure is created and passed. This means
changes made to the struct parameter inside the function do not affect the original struct.
Go
birthday(person)
// [Link] is still 30
• Pass by Reference (Using Pointers): To modify a struct inside a function, pass a pointer to
the struct.
Go
// Go allows a shortcut:
[Link]++ // The compiler automatically handles the dereference for struct pointers
}
// [Link] is now 31
• Receiver: The receiver is an extra parameter, positioned between the func keyword and the
method name. It names the value the method operates on.
• Binding: Methods can be declared on any named type in the package, except for built-in
types (like int, string, map, etc.). Structures (struct) are the most common types to attach
methods to.
Syntax
Go
Radius float64
// Usage:
c := Circle{Radius: 5}
The distinction between a standard function and a method is based entirely on the presence of a
receiver.
Declaration Starts with func funcName(...) Starts with func (receiver) methodName(...)
Association Not associated with any type. Associated with a specific named type (the receiver).
In essence: A method is Go's way of bundling behavior (the function) with data (the receiver type),
which is a key concept in object-oriented programming.
The type of receiver determines whether the method operates on a copy of the value or on the
original value.
1. Value Receiver
• Behavior: When the method is called, the receiver value (c in this case) is copied.
• Modification: Any changes made to the receiver inside the method will affect only the copy,
not the original value.
• Use Case: Use value receivers for methods that only read the receiver's data (like Area()) or
when you explicitly want the changes confined to the method's scope.
2. Pointer Receiver
• Behavior: The method receives a pointer to the receiver. No copy of the entire structure is
made.
• Modification: The method can use the pointer to modify the original receiver value.
• Use Case:
o For methods that must modify the receiver's state (like SetRadius()).
o For receivers that are large structs, using a pointer receiver is more efficient as it
avoids copying the entire struct (only the pointer's address is copied).
Receiver Type Signature Can Modify Original? Performance (Large Structs)
Pointer func (t *T) Yes More efficient (copies only the address)
Go allows you to call a method regardless of the receiver type, as long as the underlying type
matches:
• If the receiver is a value (c Circle), you can call a method with a pointer receiver
([Link](10)). Go implicitly takes the address ((&c).SetRadius(10)).
• If the receiver is a pointer (p *Circle), you can call a method with a value receiver ([Link]()).
Go implicitly dereferences the pointer ((*p).Area()).
Because functions are first-class, methods can also be treated as values and expressions.
Method Value
A method value is a function value created by binding a method to a specific instance of a type. The
resulting function value has the same arguments as the method, but no receiver.
Go
c := Circle{Radius: 7}
Method Expression
A method expression treats a method like a regular function, but it requires the receiver to be
passed explicitly as the first argument.
• Use Case: Useful for scenarios where you need to pass a specific method as a callback, but
the instance it should operate on is not yet known.
Go
areaExpr := [Link]
c := Circle{Radius: 7}
r := areaExpr(c) // Must explicitly pass the receiver 'c' as the first argument
An interface in Go is a set of method signatures. It defines a contract: any type that implements all
the methods of an interface implicitly implements that interface.
• Key Concept: Implicit Implementation: Go uses structural typing. A type does not need to
explicitly declare that it implements an interface. It just needs to have all the methods.
Definition
Go
Area() float64
Perimeter() float64
Interface Values
An interface value can hold any concrete (non-interface) value, provided that concrete value
implements the interface's methods. An interface value consists of two internal components:
2. Value: The concrete data value of the type (e.g., {Radius: 5}).
Go
c := Circle{Radius: 5}
var s Shaper
[Link]([Link]())
When you have an interface value, you often need to retrieve the underlying concrete value or check
its type.
Type Assertion
A type assertion is used to extract the concrete value stored in an interface and check if it is of a
certain type.
• Syntax (Single Return): concreteValue := i.(ConcreteType)
Go
if ok {
[Link]("Value is a string:", s)
Type Switch
A type switch is a special form of the switch statement used to perform different actions based on
the concrete type held by an interface value.
Go
case string:
case int:
default:
A type's method set is the collection of methods that can be called on a value of that type. This set is
crucial for determining which types satisfy an interface.
Receiver Type Value Type (T) Method Set Pointer Type (*T) Method Set
1. Value T satisfies Interface I: If the method set of T contains all methods of I. This means T
can only have methods with value receivers.
2. Pointer *T satisfies Interface I: If the method set of *T contains all methods of I. Since the
method set of *T includes all methods (both value and pointer receivers), a pointer to a type
will always satisfy an interface if the type itself has all the required methods.
Practical Rule: To ensure a type satisfies an interface, especially when modification is needed
(pointer receivers), always use the pointer type when assigning to the interface variable.
Interfaces can be composed by embedding one or more interfaces into a new interface. The resulting
interface is the union of all methods from the embedded interfaces.
• Benefit: Allows for the creation of new, larger contracts without repeating method
signatures.
Go
// Interface 1
// Interface 2
The ReadWriter interface requires any concrete type that implements it to provide Read() and
Write() methods.
• Universal Interface: Since it has no methods, every concrete type satisfies the empty
interface.
• Use Cases:
1. Holding Unknown Types: Used in functions (like [Link] or logging) that need to
accept arguments of any type.
Go
var i interface{}
// To use the value, you MUST use a type assertion or type switch:
s, ok := i.(string)
if ok {
[Link](s)
That's an excellent final unit! Go's approach to Concurrency using Goroutines and Channels is one of
its most defining and powerful features. This unit will introduce you to handling parallel tasks
efficiently.
Here are the detailed and fully explained notes for your Unit V syllabus:
It's crucial to understand the difference between these two concepts in the context of Go:
Definition Dealing with many things at once. Doing many things at once.
Feature Concurrency Parallelism
Tasks make progress by taking turns Tasks execute simultaneously (e.g., on multiple
Execution
(e.g., on a single core). cores).
In Go: Concurrency is about structuring your code (using Goroutines), and the Go runtime handles
the parallelism based on the number of available CPU cores.
• Lightweight: Goroutines are much cheaper than operating system threads, requiring only a
small amount of memory (initially a few KB of stack space). Go can run hundreds of
thousands of Goroutines efficiently.
Goroutine Syntax
Go
[Link]("Worker:", id)
func main() {
go worker(1)
go func() {
}()
The main function (or any calling function) does not wait for spawned Goroutines to complete. If the
main function exits, the program terminates immediately, even if other Goroutines are still running.
The [Link] type is used to block execution until a set of Goroutines completes.
• Methods:
o [Link](): Decrements the counter (usually called via defer in the Goroutine).
Go
import (
"fmt"
"sync"
"time"
func main() {
var wg [Link]
go func(id int) {
[Link]([Link])
}
5.4 Channels
Channels are the fundamental pipes through which Goroutines communicate. They provide a safe,
synchronized way to send and receive values between concurrently executing functions.
• Go's Philosophy: "Do not communicate by sharing memory; instead, share memory by
communicating." Channels embody this principle.
• Creation: Channels are created using the make function, specifying the type of data they will
carry.
Go
• Send: A send operation blocks until a corresponding receiver is ready to take the value.
Go
func main() {
ch := make(chan string)
go func() {
[Link]([Link])
}()
[Link](result)
Unbuffered Channels
• Synchronization: Strictly synchronous. Sender and receiver must be ready at the exact same
time for the transfer to happen.
Buffered Channels
• Behavior: The channel can hold up to $N$ values before a send operation blocks.
Go
// This will succeed immediately because the buffer is not full (2/2 capacity)
bufCh <- 1
bufCh <- 2
// bufCh <- 3
Functions often need to restrict how they interact with a channel (either sending or receiving, but
not both). This is achieved using directional channel types, which provide type safety.
• Receive-only: Declared with <-chan T. The function can only receive data.
• Send-only: Declared with chan<- T. The function can only send data.
Go
c <- "message"
}
msg := <-c
[Link](msg)
func main() {
ch := make(chan string)
go sender(ch)
receiver(ch) // Implicitly passes a two-way channel, but function signature restricts usage
Note: A bidirectional channel (chan T) can be passed to a function that accepts a directional channel,
but the reverse is not true.
The select statement is Go's way of waiting on multiple channel operations simultaneously. It blocks
until one of its cases is ready, then executes that case.
• Random Selection: If multiple channels are ready, select chooses one at random (ensuring
fairness).
Go
func main() {
select {
[Link]("Received:", msg2)
// default:
The time package provides utility Goroutines and channels for handling time-based actions.
Timers
A Timer is a one-time event that sends a value on a channel after a specified duration.
Go
[Link]("Timer fired!")
Tickers
A Ticker is similar to a timer, but it sends a value on a channel at regular intervals. It is used for
scheduled, recurring events.
Go
go func() {
for {
select {
case <-done:
return
[Link]("Tick at:", t)
}()
[Link]()
[Link]("Ticker stopped.")
This final unit covers how Go projects are structured, how to manage dependencies, and the crucial
practices of testing and file handling. These topics are essential for building maintainable and
professional applications.
Here are the detailed and fully explained notes for your Unit VI syllabus:
Packages
A package is the fundamental unit of organization and code reuse in Go. Every Go program is made
up of packages.
• Structure: All source files (.go files) within a single directory must belong to the same
package.
• Package Name: The package name is declared at the top of every source file: package
packageName.
o Executable Packages: An executable program must have a package named main. The
entry point for execution is the main function in the main package.
o Library Packages: All other packages are library packages, and their names typically
match the directory name.
While older Go used a strict $GOPATH workspace, modern Go (since version 1.11) uses Modules.
• Module: A module is a collection of related Go packages stored in a file directory tree with a
[Link] file at its root.
• [Link]: This file defines the module's path (name), the required version of Go, and its
dependencies (other modules it uses).
Go uses a simple rule for controlling visibility (public/private) across packages: capitalization.
• Exported (Public): An identifier (variable, function, struct field, type, or method) is exported
(accessible from other packages) if its name begins with a capital letter.
Go
• Unexported (Private): If an identifier starts with a lowercase letter, it is unexported and can
only be accessed within the package where it is defined.
Import Paths
Packages are brought into scope using the import keyword, specifying the package's module path.
Go
import (
"fmt"
"net/http"
"myproject/utils"
)
Named Imports
You can provide an alternate name (alias) for an imported package to resolve naming conflicts or use
a shorter name.
Go
Go provides a special function called init() that runs before the main() function and before any
package's exported functions are called.
• Execution Order:
• Multiple init: A package can have multiple init functions (in one file or spread across files).
They are executed in lexical file name order.
A blank import uses the blank identifier (_) as the package alias.
• Purpose: This tells the Go compiler to import the package solely for its side effects, meaning
the package's init() function is executed, but none of its exported identifiers are used in the
importing file.
• Example: Often used to initialize database drivers or runtime packages that register
themselves upon initialization.
Go
import (
"database/sql"
_ "[Link]/lib/pq" // Registers the postgres driver via its init() function
// We now use the 'sql' package, but never directly call the 'pq' package.
Testing is integral to Go development. The go test command automatically finds and runs tests.
• Test Function Signature: Test functions must begin with Test and take a single argument of
type *testing.T.
Go
• Testing Package: Test functions typically reside in a separate package (e.g., package
calculator_test) which allows them to only access exported identifiers, mimicking external
usage.
o [Link](...) or [Link](...): Logs the error and stops the test immediately.
Table Tests
Table testing is the idiomatic way to write robust Go tests. It uses a slice of structs (the "test table")
to define inputs, expected outputs, and test descriptions for a function.
• Benefits: Reduces boilerplate, makes it easy to add new cases, and centralizes test data.
Go
tests := []struct {
name string
a, b int
want int
}{
{"positive", 1, 2, 3},
{"zero", 0, 5, 5},
}
for _, tt := range tests {
if got != [Link] {
})
While table tests are deterministic, random tests or fuzz testing involve generating random inputs to
uncover edge cases that manual testing might miss.
• Fuzzing: Go has built-in support for fuzz testing (since Go 1.18) where the Go toolchain
automatically generates random inputs to feed to a designated func FuzzT(f *testing.F)
function.
• Benefits: Highly effective for finding bugs related to integer overflow, empty inputs, or
specific byte sequences (e.g., security vulnerabilities).
6.8 Benchmarking
Go's testing framework supports benchmarking to measure the performance of code. Benchmarks
must be in a _test.go file.
• Benchmark Function Signature: Must begin with Benchmark and take an argument of type
*testing.B.
Go
• Loop: The code being benchmarked must be placed inside a loop that runs b.N times. The
value of b.N is adjusted automatically by the go test runner to ensure the test runs for a
statistically significant duration.
Go
Go's standard library provides robust tools for file system interaction, primarily in the os and io
packages.
Function Purpose
[Link](name string) Creates a new file (truncates if it exists) and returns an *[Link].
1. Reading Entire Files (Simple): Use [Link]() (Go 1.16+) or [Link]() (older).
Go
// data is []byte
2. Writing Entire Files (Simple): Use [Link]() (Go 1.16+) or [Link]() (older).
Go
3. Streaming I/O (Efficient): For large files, use the *[Link] methods along with bufio (buffered
I/O) or [Link]/[Link] interfaces.
Go
scanner := [Link](file)
for [Link]() {
line := [Link]()
[Link](line)