0% found this document useful (0 votes)
17 views40 pages

Go Programming Notes

The document provides an introduction to Go programming fundamentals, covering topics such as the Go runtime, compilation, keywords, identifiers, constants, variables, operators, control flow, and functions. It explains how Go manages memory with garbage collection, supports concurrency with goroutines, and allows for fast compilation and cross-compilation. Additionally, it details the syntax and usage of various programming constructs, including data types, control flow statements, and function definitions.

Uploaded by

harishgore552
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
17 views40 pages

Go Programming Notes

The document provides an introduction to Go programming fundamentals, covering topics such as the Go runtime, compilation, keywords, identifiers, constants, variables, operators, control flow, and functions. It explains how Go manages memory with garbage collection, supports concurrency with goroutines, and allows for fast compilation and cross-compilation. Additionally, it details the syntax and usage of various programming constructs, including data types, control flow statements, and function definitions.

Uploaded by

harishgore552
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

Unit I: Introduction (Go Programming Fundamentals)

1.1 Go Runtime and Compilation

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.

• Scheduling: Go uses a sophisticated scheduler to manage lightweight, concurrent functions


called goroutines. It maps many goroutines onto a smaller number of operating system
threads ($M:N$ scheduling).

• 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).

• Cross-Compilation: Go excels at cross-compilation. You can easily build an executable for a


different operating system and architecture from your current machine using environment
variables like GOOS and GOARCH.

Example: To compile for Linux on an ARM processor:

GOOS=linux GOARCH=arm go build your_program.go

1.2 Keywords and Identifiers

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

break default func interface select


Go Keywords

case defer go map struct

chan else goto package switch

const fallthrough if range type

continue for import return var

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.

• Exported vs. Unexported:

o An identifier starting with a capital letter (e.g., Name, CalculateTotal) is exported and
visible/accessible from outside its package.

o An identifier starting with a lowercase letter (e.g., name, calculateTotal) is


unexported (private) and only accessible within its own package.

1.3 Constants and Variables

Constants

Constants are fixed values that cannot be changed during the execution of a program.

• Declaration: Declared using the const keyword.

Go

const Pi = 3.14159

const Greeting string = "Hello"

• 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.

• Declaration with var:

Go

var name string = "Alice" // Explicit type and initial value

var age int // Default value (0 for int)

• Type Inference: Go can often infer the type from the initial value:

Go

var message = "Welcome" // Inferred as string

• Short Variable Declaration (:=): The most common way to declare and initialize variables
inside a function. It cannot be used for package-level variables.

Go

count := 10 // Declares and initializes count as an int

• Multiple Declaration:

Go

var x, y int = 1, 2

a, b := "first", true

1.4 Operators and Expressions

Operators

Operators are special symbols or keywords that perform operations on one or more operands.

• Arithmetic: +, -, *, /, % (modulus).

• Comparison (Relational): ==, !=, >, <, >=, <= (Result is a boolean).

• Logical: && (AND), || (OR), ! (NOT) (Used with boolean operands).

• Bitwise: & (AND), | (OR), ^ (XOR), &^ (Bit Clear), << (Left Shift), >> (Right Shift).

• Assignment: =, +=, -=, *=, /=, %=, etc.

Expressions

An expression is a combination of operators and operands that evaluates to a single value.

Examples:

• a + b (Arithmetic expression, evaluates to a numeric value)

• age > 18 (Comparison expression, evaluates to a boolean value)

• calculateArea(length, width) (Function call expression, evaluates to the return value of the
function)

1.5 Local Assignments


Local assignments refer to assigning values to variables defined within the scope of a function or a
block (like an if or for block).

• 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.

• Multiple Assignment: Go allows multiple values to be assigned in a single line. This is


commonly used for:

1. Swapping values without a temporary variable:

Go

x, y = y, x // Swaps the values of x and y

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)

1.6 Booleans, Numeric, Characters

Booleans

• Type: bool

• Values: true or false

• Default Value: false

• Usage: Essential for control flow (if, for) and logical operations.

Go

var isActive bool = true

isOld := age > 60

Numeric Types

Go provides signed integers, unsigned integers, and floating-point numbers.


Size
Type Description
(Bits)

int, uint 32 or 64 Platform-dependent integer (preferred general-purpose int)

int8, uint8 8 Integers from -128 to 127 / 0 to 255

int16, uint16 16

int32, uint32 32 int32 is an alias for rune (character)

int64, uint64 64

float32 32 Single-precision floating point

Double-precision floating point (preferred general-purpose


float64 64
float)

complex64,
Complex numbers
complex128

uintptr Integer large enough to hold the bit pattern of any pointer

Characters (rune)

• Type: rune (an alias for int32).

• Purpose: Represents a Unicode code point (a single character).

• Declaration: Use single quotes.

Go

var initial rune = 'G'

1.7 Pointers and Addresses

A pointer is a variable that stores the memory address of another variable.

• Zero Value: nil (meaning it points to nothing).

• Address-of Operator (&): Used to get the memory address of a variable.

Go

x := 42

p := &x // p is a pointer to x (type *int)

• Dereference Operator (*): Used to access (read or modify) the value stored at the memory
address the pointer holds.

Go

[Link](*p) // Prints 42 (the value at the address p points to)


*p = 21 // Changes the value of x to 21

• Type: A pointer to a variable of type $T$ is denoted as $*T$.

Go

var ptr *int // Declares a pointer to an integer

• Go Pointers vs. C Pointers: Go pointers are safer. You cannot perform pointer arithmetic (like
p++) in Go.

1.8 Strings

A string in Go is a sequence of read-only bytes.

• 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).

o Accessing by index (e.g., s[0]) accesses the byte at that index.

Go

s := "Hello, Go"

// Loop over runes (characters)

for index, char := range s {

[Link]("Index %d, Rune: %c\n", index, char)

1.9 if-else, switch, for loop (Control Flow)

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 if score >= 80 {


// ...

} else {

// ...

• Short Statement: An optional short statement can precede the condition. Variables declared
here are scoped only to the if and else blocks.

Go

if result, err := someFunc(); err != nil {

// Handle error...

} else {

// Use result...

// result and err are *not* accessible here

switch Statement

A concise way to express a series of if-else checks on the same expression.

• Implicit break: Unlike C/C++, Go's switch automatically executes a break after a matching
case.

• Case List: A case can contain a comma-separated list of values.

• Expressionless switch (Treats it like if-else-if):

Go

t := [Link]().Hour()

switch { // Missing expression is equivalent to 'switch true'

case t < 12:

[Link]("Morning")

case t < 18:

[Link]("Afternoon")

default:

[Link]("Evening")

for Loop

The for loop is the only looping construct in Go (no while or do-while).

• C-style for (Initialization; Condition; Post-statement):


Go

for i := 0; i < 10; i++ {

// ...

• while-style for (Condition only):

Go

sum := 1

for sum < 1000 { // Loop as long as the condition is true

sum += sum

• Infinite Loop:

Go

for { // Loop forever until a break or return

// ...

1.10 Iterations (The for range Loop)

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:

1. Index/Key: The index (for arrays/slices/strings) or key (for maps).

2. Value: The element value at that index/key.

Collection Type Index/Key Value Value Value

Slice/Array Index (int) Copy of the element at that index

String Starting byte index of the rune The rune (Unicode code point)

Map The map key The map value

Channel None The item received from the channel

Go

numbers := []int{10, 20, 30}

// Standard iteration over slices

for i, num := range numbers {


[Link]("Index: %d, Value: %d\n", i, num)

// Ignore the index (use '_' as the blank identifier)

for _, num := range numbers {

// ...

1.11 Using break and continue

break Statement

The break statement immediately terminates the innermost for, switch, or select statement it is
contained in.

• In Loops: Jumps execution to the line immediately following the loop.

• 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

for i := 0; i < 10; i++ {

if i%2 != 0 {

continue // Skip odd numbers (the rest of the loop body is skipped)

if i == 8 {

break // Stop the loop entirely

[Link](i) // Prints: 0, 2, 4, 6

Labeled break (Advanced)

A break statement can be followed by a label to terminate an outer loop when working with nested
loops.

Go

OuterLoop: // Label for the outer loop


for i := 0; i < 3; i++ {

for j := 0; j < 3; j++ {

if i == 1 && j == 1 {

break OuterLoop // Jumps out of BOTH loops to the line after the OuterLoop

[Link](i, j)

Unit II: Functions

2.1 Parameters and Return Values

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

func functionName(param1 type1, param2 type2) { ... }

• Concise Syntax (Same Type): If consecutive parameters share the same type, you only need
to specify the type once for the last parameter.

Go

func add(x int, y int) int { ... }

// Can be written as:

func add(x, y int) int {

return x + y

Return Values (Outputs)

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).

• Single Return Value:


Go

func square(num int) int {

return num * num

• Multiple Return Values:

Go

func swap(x, y string) (string, string) {

return y, x // Returns two strings

2.2 Call by Value and Reference

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

func changeValue(num int) {

num = num + 10 // Changes the COPY of 'num'

x := 5

changeValue(x)

// x is still 5

Simulating "Call by Reference" (Using Pointers)

While Go is call-by-value, you can achieve "call by reference" behavior by passing a pointer (the
memory address) to the function.

• The pointer itself is passed by value (a copy of the address is made).

• However, the function can use the dereference operator (*) on the pointer to access and
modify the original data at that address.

Go

func changeReference(ptr *int) {

*ptr = *ptr + 10 // Changes the VALUE at the address pointed to by 'ptr'


}

y := 5

changeReference(&y) // Pass the address of y

// 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.

2.3 Named Return Variables

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

// A naked return returns the current values of sum and diff

return

// Calling it: s, d := calculate(10, 5) // s=15, d=5

Note: While convenient for short functions, use naked returns sparingly in long functions, as they can
reduce clarity.

2.4 Blank Identifiers

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.

• Discarding Multiple Return Values:

Go

value, _ := someFunctionThatReturnsTwoThings() // Discard the second return value (e.g., an error)

• Discarding Loop Index/Value:


Go

data := []string{"A", "B", "C"}

for _, item := range data { // Discard the index

[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"

2.5 Variable Argument Parameters (...)

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

for _, n := range nums {

total += n

return total

// Calling it:

// sumAll(1, 2, 3) // nums = []int{1, 2, 3}

// sumAll() // nums = []int{}

• 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

myNumbers := []int{10, 20}

result := sumAll(myNumbers...) // Unfurls the slice into individual arguments

2.6 Using defer Statements

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.

• Typical Use Cases:

1. Cleanup/Resource Release: Ensuring resources (like files, database connections,


locks) are closed or released, regardless of how the function exits (normal return or
panic).

2. Timing: Measuring function execution time.

Go

func processFile(filename string) {

file, err := [Link](filename)

if err != nil {

return

// 1. Defer call pushed onto stack. Guarantees [Link]() runs.

defer [Link]()

// 2. This runs next:

[Link]("Reading file...")

// 3. Deferred call executes when function returns.

Key Point: The arguments to a deferred function are evaluated when the defer statement is
executed, not when the deferred call is executed.

2.7 Recursive Functions

A function is recursive if it calls itself.

• Requirements for Recursion:

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.

• Example: Factorial ($n! = n \times (n-1)!$)

Go

func factorial(n int) int {

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.

2.8 Functions as Parameters (First-Class Functions)

Functions in Go are first-class citizens, meaning they can be treated just like any other value (like an
int or a string).

• Functions can be:

1. Assigned to variables.

2. Passed as arguments to other functions (Parameters).

3. Returned as values from other functions.

Passing Functions as Parameters

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

// Define a function type called MathOperation

type MathOperation func(int, int) int

// The function that accepts the MathOperation function type

func executeOperation(a, b int, op MathOperation) int {

return op(a, b) // Executes the passed function 'op'

// A function that matches the MathOperation type

func multiply(x, y int) int {

return x * y

}
// Calling it:

// result := executeOperation(5, 4, multiply) // result = 20

Unit III: Working with Data (Arrays, Slices, and Structures)

3.1 Array Literals

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

var arrayName [size]type

• Zero Value: When declared without initialization, all elements are set to the zero value of
their type (e.g., 0 for int, "" for string).

• Array Literals: A compact way to declare and initialize an array.

Go

// 1. Full Literal (size is explicit)

var ages [5]int = [5]int{20, 21, 22, 23, 24}

// 2. Short Declaration and Literal

names := [3]string{"Alice", "Bob", "Charlie"}

// 3. Ellipsis (...) Literal (size is inferred by the compiler)

scores := [...]float64{95.5, 88.0, 76.5} // size is 3

• Accessing Elements: Use square brackets with a zero-based index.

Go

firstAge := ages[0] // 20

3.2 Multidimensional Arrays

A multidimensional array is an array whose elements are themselves arrays. They are often used to
represent grids or tables.

• Declaration Syntax (2D Array):

Go
var arrayName [rows][cols]type

• Literal Example (3x2 Integer Grid):

Go

grid := [3][2]int{

{1, 2}, // Row 0

{3, 4}, // Row 1

{5, 6}, // Row 2

• Accessing Elements: Use an index for each dimension.

Go

value := grid[1][0] // Accesses element at row 1, column 0 (which is 3)

3.3 Array Parameters

When an array is passed to a function, it is passed by value.

• 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.

o Implication 2: Passing large arrays is inefficient because it involves copying a large


block of memory.

Go

func modifyArray(arr [3]int) { // Takes a copy of a [3]int

arr[0] = 99

data := [3]int{1, 2, 3}

modifyArray(data)

// data is still [1, 2, 3]

Note: Due to the performance cost and fixed size, arrays are less common in Go programming than
slices (see 3.4).

3.4 Slices and Slice Parameters

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.

• Slice Structure: A slice is a three-item data structure (header) that includes:


1. Pointer: Points to the starting element of the underlying array.

2. Length: The number of elements currently in the slice.

3. Capacity: The number of elements in the underlying array, starting from the slice's
pointer, that the slice can include.

• Declaration and Creation:

1. Slice Literal: Creates an underlying array and returns a slice that references it.

Go

s := []int{1, 2, 3, 4} // No size specified in the brackets

2. make function: Used to create a slice, specifying its length and optional capacity.

Go

a := make([]int, 5) // len=5, cap=5 (all elements are 0)

b := make([]int, 0, 10) // len=0, cap=10

3. Slicing an Array/Slice: Creates a new slice header pointing to a part of the original
data.

Go

arr := [5]int{10, 20, 30, 40, 50}

subSlice := arr[1:4] // [20, 30, 40]. Indices 1 (inclusive) to 4 (exclusive).

Slice Parameters (Reference Behavior)

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

func modifySlice(s []int) {

s[0] = 99 // Modifies the underlying array, affecting the caller's slice

data := []int{1, 2, 3}

modifySlice(data)

// data is now [99, 2, 3]

3.5 Multidimensional Slices


A multidimensional slice (or slice of slices) is the Go equivalent of a jagged array (where inner slices
can have different lengths).

• Declaration (2D Slice):

Go

var matrix [][]int

• Creation: You initialize the outer slice, and then initialize each inner slice separately.

Go

// Create an outer slice of 3 elements

matrix := make([][]int, 3)

// Initialize the inner slices with varying lengths

matrix[0] = []int{1, 2}

matrix[1] = []int{3, 4, 5}

matrix[2] = []int{6}

// Accessing elements

val := matrix[1][2] // 5

3.6 Structures and Structure Parameters

A structure (struct) is a user-defined composite type that groups together fields (variables) of
different types into a single unit.

• Definition (Type Declaration):

Go

type Person struct {

FirstName string

LastName string

Age int

• Initialization (Struct Literals):

1. By Field Name (Recommended for readability):

Go

p1 := Person{

FirstName: "Alice",
LastName: "Smith",

Age: 30,

2. Positional (Requires knowing the exact order):

Go

p2 := Person{"Bob", "Jones", 25}

3. Zero Value:

Go

var p3 Person // FirstName="", LastName="", Age=0

• Accessing Fields: Use the dot operator (.).

Go

[Link]([Link]) // Alice

[Link] = 31 // Modify field value

Structure Parameters

Structures are passed by value to functions, similar to arrays.

• 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

func birthday(p Person) { // Takes a copy of the Person struct

[Link]++ // Changes the copy's Age

person := Person{Age: 30}

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

func realBirthday(p *Person) { // Takes a pointer to the Person struct

(*p).Age++ // Dereference the pointer and increment the Age

// Go allows a shortcut:

[Link]++ // The compiler automatically handles the dereference for struct pointers
}

person := Person{Age: 30}

realBirthday(&person) // Pass the address

// [Link] is now 31

Unit IV: Methods and Interfaces

4.1 Method Declarations

A method in Go is simply a function that is bound to a specific type. It is declared by including a


receiver argument in the function definition.

• 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

// 1. Define a named type (e.g., a struct)

type Circle struct {

Radius float64

// 2. Declare a method (Area) on the Circle type.

// 'c Circle' is the receiver.

func (c Circle) Area() float64 {

return 3.14159 * [Link] * [Link]

// Usage:

c := Circle{Radius: 5}

area := [Link]() // Method call syntax


• Method Name Collision: Two different types can have methods with the same name (e.g.,
[Link]() and [Link]()). The method is scoped to its receiver type.

4.2 Functions vs. Methods

The distinction between a standard function and a method is based entirely on the presence of a
receiver.

Feature Function Method

Declaration Starts with func funcName(...) Starts with func (receiver) methodName(...)

Association Not associated with any type. Associated with a specific named type (the receiver).

Call Syntax [Link](arg1) [Link](arg1)

Receiver None. Must have one (value or pointer type).

Purpose General-purpose logic. Defines behavior specific to a particular data type.

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.

4.3 Pointer and Value Receivers

The type of receiver determines whether the method operates on a copy of the value or on the
original value.

1. Value Receiver

func (c Circle) Area() float64 { ... }

• 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

func (c *Circle) SetRadius(r float64) { ... }

• 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)

Value func (t T) No Less efficient (copies entire data)

Pointer func (t *T) Yes More efficient (copies only the address)

Compiler Flexibility (Syntactic Sugar)

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()).

4.4 Method Values and Expressions

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}

// 'areaFunc' is now a function value of type 'func() float64'

areaFunc := [Link] // Method value: c is already bound to the call

[Link](areaFunc()) // Call it without a receiver

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.

• Type: func (T) MethodType(...)

• 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' is a function of type 'func(Circle) float64'

areaExpr := [Link]

c := Circle{Radius: 7}
r := areaExpr(c) // Must explicitly pass the receiver 'c' as the first argument

4.5 Interface Types and Values

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

// Define the interface

type Shaper interface {

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:

1. Type: The concrete type held by the interface (e.g., Circle).

2. Value: The concrete data value of the type (e.g., {Radius: 5}).

Go

c := Circle{Radius: 5}

// s is an interface value of type Shaper

var s Shaper

s = c // s now holds (Type: Circle, Value: {Radius: 5})

// You can only call methods defined in the Shaper interface on s.

[Link]([Link]())

4.6 Type Assertions and Type Switches

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)

o If the assertion fails (the type is incorrect), the program panics.

• Syntax (Two Return Values - Idiomatic): concreteValue, ok := i.(ConcreteType)

o ok is a boolean indicating success. This is the preferred safe method.

Go

var i interface{} = "hello"

s, ok := i.(string) // s="hello", ok=true

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

func inspect(i interface{}) {

switch v := i.(type) { // 'i.(type)' can ONLY be used in a switch statement

case string:

[Link]("It's a string, length %d\n", len(v))

case int:

[Link]("It's an integer, value %d\n", v*2)

default:

[Link]("Unknown type: %T\n", v)

4.7 Method Sets with Interfaces

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

Value Receiver (func (T) Method) Included Included

Pointer Receiver (func (*T) Method) Not included Included


Rules for Interface Satisfaction

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.

4.8 Embedded Interfaces

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

type Reader interface {

Read(p []byte) (n int, err error)

// Interface 2

type Writer interface {

Write(p []byte) (n int, err error)

// Embedded Interface (combines Reader and Writer)

type ReadWriter interface {

Reader // All methods of Reader (Read)

Writer // All methods of Writer (Write)

// ReadWriter also needs to implement Read and Write

The ReadWriter interface requires any concrete type that implements it to provide Read() and
Write() methods.

4.9 Empty Interfaces


The empty interface is an interface that specifies zero methods.

• Declaration: interface{} (or the new alias any)

• 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.

2. Generic Data Structures: Used in slices or maps to store values of heterogeneous


types (though generics are now the preferred solution).

Go

var i interface{}

i = 42 // i now holds (Type: int, Value: 42)

i = "Go rocks" // i now holds (Type: string, Value: "Go rocks")

// 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:

Unit V: Goroutines and Channels (Concurrency)

5.1 Concurrency vs. Parallelism

It's crucial to understand the difference between these two concepts in the context of Go:

Feature Concurrency Parallelism

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).

Structure the program to handle Improve program throughput/speed using


Goal
multiple tasks logically. hardware resources.

Achieved using Goroutines and Achieved by the Go Scheduler running Goroutines


Go Model
Channels. on multiple OS threads/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.

5.2 Goroutine Functions and Lambdas

A Goroutine is a lightweight, independent function execution context. It's essentially a function


running concurrently with other code.

• 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.

• Creation: A Goroutine is started by preceding a function call with the go keyword.

Goroutine Syntax

Go

func worker(id int) {

[Link]("Worker:", id)

func main() {

// 1. Starting a named function as a Goroutine

go worker(1)

// 2. Starting an anonymous function (lambda) as a Goroutine

go func() {

[Link]("Anonymous Goroutine started")

}()

// The main function doesn't wait for Goroutines to finish naturally.

// We must wait explicitly (see Wait Groups below).


}

5.3 Wait Groups

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](delta int): Adds a counter to the WaitGroup.

o [Link](): Decrements the counter (usually called via defer in the Goroutine).

o [Link](): Blocks execution until the counter reaches zero.

Go

import (

"fmt"

"sync"

"time"

func main() {

var wg [Link]

for i := 1; i <= 3; i++ {

[Link](1) // Increment the counter for each Goroutine

go func(id int) {

defer [Link]() // Decrement the counter when the function returns

[Link]([Link])

[Link]("Worker finished:", id)

}(i) // Pass i as an argument to avoid closure issues

[Link]() // Block until all [Link]() calls have been made

[Link]("All workers complete.")

}
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

// Create a channel that carries integer values

messages := make(chan int)

5.5 Sending and Receiving

The channel operator (<-) is used to send and receive data.

• Send: channel <- value (Sends a value into the channel)

• Receive: value := <- channel (Receives a value from the channel)

Blocking Behavior: By default, channel operations are synchronizing (blocking):

• Send: A send operation blocks until a corresponding receiver is ready to take the value.

• Receive: A receive operation blocks until a sender is ready to send a value.

Go

func main() {

ch := make(chan string)

// Goroutine sends a value after 1 second

go func() {

[Link]([Link])

ch <- "data arrived" // Blocks until main receives

}()

// Main Goroutine receives the value

result := <-ch // Blocks until the Goroutine sends

[Link](result)

5.6 Unbuffered and Buffered Channels


The capacity of a channel determines whether it is buffered or unbuffered.

Unbuffered Channels

• Capacity: 0 (Default, if not specified in make).

• Synchronization: Strictly synchronous. Sender and receiver must be ready at the exact same
time for the transfer to happen.

Buffered Channels

• Capacity: $N > 0$.

• Behavior: The channel can hold up to $N$ values before a send operation blocks.

o Send: Blocks only when the buffer is full.

o Receive: Blocks only when the buffer is empty.

Go

// Unbuffered channel (Synchronous)

unbufCh := make(chan int)

// Buffered channel with capacity 2

bufCh := make(chan int, 2)

// This will succeed immediately because the buffer is not full (2/2 capacity)

bufCh <- 1

bufCh <- 2

// This would block because the buffer is full

// bufCh <- 3

5.7 Directional Channels

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

// This function can only SEND to the channel

func sender(c chan<- string) {

c <- "message"
}

// This function can only RECEIVE from the channel

func receiver(c <-chan string) {

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.

5.8 Multiplexing with select

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.

• Analogy: It's like a switch statement, but for channels.

• Random Selection: If multiple channels are ready, select chooses one at random (ensuring
fairness).

Go

func main() {

ch1 := make(chan string)

ch2 := make(chan string)

go func() { [Link](1 * [Link]); ch1 <- "one" }()

go func() { [Link](2 * [Link]); ch2 <- "two" }()

for i := 0; i < 2; i++ {

select {

case msg1 := <-ch1:


[Link]("Received:", msg1)

case msg2 := <-ch2:

[Link]("Received:", msg2)

// Optional: The 'default' case executes immediately if no other case is ready.

// default:

// [Link]("No message received yet.")

5.9 Timers and Tickers

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.

1. Create the timer: timer := [Link](duration)

2. Wait for the signal: <-timer.C (the channel)

Go

timer := [Link](2 * [Link])

[Link]("Waiting for 2 seconds...")

<-timer.C // Blocks until the timer's channel receives a value

[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.

1. Create the ticker: ticker := [Link](interval)

2. Wait for the signal: <-ticker.C (the channel)

3. Stop the ticker when done: [Link]()

Go

ticker := [Link](500 * [Link])

done := make(chan bool)

go func() {
for {

select {

case <-done:

return

case t := <-ticker.C: // Fired every 500ms

[Link]("Tick at:", t)

}()

[Link](2 * [Link]) // Let it tick 4 times

[Link]()

done <- true

[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:

Unit VI: Packages and Files

6.1 Packages and Workspaces

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.

Workspaces and Modules (Modern Go)

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).

• Dependency Management: The Go toolchain manages dependencies automatically,


downloading and storing required modules outside of your project directory, making projects
portable.

• Initiating a Module: go mod init <module_path>

6.2 Exporting Package Names

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

// Inside mathutil package

func CalculateSum(a, b int) int { ... } // Exported

func calculateAverage(a, b int) float64 { ... } // Not exported (internal)

• 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.

6.3 Import Paths and Named Imports

Import Paths

Packages are brought into scope using the import keyword, specifying the package's module path.

• Standard Library: import "fmt"

• Local/Custom Modules: import "myproject/models"

• Grouped Imports (Idiomatic):

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.

• Syntax: import aliasName "full/path/to/package"

Go

import log "[Link]/sirupsen/logrus"

// Use the package as '[Link](...)'

6.4 Package Initializations

Go provides a special function called init() that runs before the main() function and before any
package's exported functions are called.

• Execution Order:

1. All imported packages are initialized (recursively).

2. All init() functions within the imported packages are executed.

3. All init() functions in the main package are executed.

4. The main() function is executed.

• Multiple init: A package can have multiple init functions (in one file or spread across files).
They are executed in lexical file name order.

• Purpose: init functions are typically used for:

o Initializing complex package-level variables.

o Performing registration tasks (e.g., registering database drivers).

o Running sanity checks before execution begins.

6.5 Blank Imports

A blank import uses the blank identifier (_) as the package alias.

• Syntax: import _ "full/path/to/package"

• 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.

6.6 Unit Testing with Test Functions

Testing is integral to Go development. The go test command automatically finds and runs tests.

• Naming Convention: Test files must end with _test.go.

• Test Function Signature: Test functions must begin with Test and take a single argument of
type *testing.T.

Go

func TestFunctionName(t *testing.T) { ... }

• 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.

• Reporting Failures: The *testing.T type provides methods to report errors:

o [Link](...) or [Link](...): Logs the error and continues the test.

o [Link](...) or [Link](...): Logs the error and stops the test immediately.

6.7 Table Tests and Random Tests

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

func TestAdd(t *testing.T) {

tests := []struct {

name string

a, b int

want int

}{

{"positive", 1, 2, 3},

{"negative", -1, -2, -3},

{"zero", 0, 5, 5},

}
for _, tt := range tests {

// Run the test case in isolation with [Link]()

[Link]([Link], func(t *testing.T) {

got := Add(tt.a, tt.b)

if got != [Link] {

[Link]("Add(%d, %d) = %d, want %d", tt.a, tt.b, got, [Link])

})

Random Tests (Fuzzing/Property Testing)

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

func BenchmarkFunctionName(b *testing.B) { ... }

• 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.

• Running Benchmarks: go test -bench=. (or a specific regex)

Go

func BenchmarkCalculateSum(b *testing.B) {

// b.N is automatically adjusted by the go test runner

for i := 0; i < b.N; i++ {


CalculateSum(100, 200) // The code whose performance we measure

6.9 Working with Files

Go's standard library provides robust tools for file system interaction, primarily in the os and io
packages.

File Operations (os package)

Function Purpose

[Link](name string) Creates a new file (truncates if it exists) and returns an *[Link].

[Link](name string) Opens a file for reading and returns an *[Link].

[Link](name, flag, Opens a file with specified flags (Read/Write/Append) and


perm) permissions.

[Link](name string) Deletes a file or directory (must be empty).

Reading and Writing

1. Reading Entire Files (Simple): Use [Link]() (Go 1.16+) or [Link]() (older).

Go

data, err := [Link]("[Link]")

// data is []byte

2. Writing Entire Files (Simple): Use [Link]() (Go 1.16+) or [Link]() (older).

Go

err := [Link]("[Link]", []byte("Hello Go"), 0644) // 0644 is file permission

3. Streaming I/O (Efficient): For large files, use the *[Link] methods along with bufio (buffered
I/O) or [Link]/[Link] interfaces.

Go

file, err := [Link]("[Link]")

if err != nil { /* handle error */ }

defer [Link]() // ALWAYS defer closing the file

// Create a buffered reader for efficient line-by-line reading

scanner := [Link](file)

for [Link]() {
line := [Link]()

[Link](line)

You might also like