Go Reference: Comprehensive Developer's Guide

// --------------------------------------------------------------------------------
// Go Reference and Guide
//
// ReferenceCollection.com
// Licensed under CC BY-SA
// --------------------------------------------------------------------------------

// TABLE OF CONTENTS
// -----------------
// 1.  Introduction to Go
// 2.  Basic Syntax and Structure
// 3.  Data Types and Variables
// 4.  Operators and Expressions
// 5.  Control Flow Statements
// 6.  Functions
// 7.  Arrays and Slices
// 8.  Maps
// 9.  Structs
// 10. Pointers
// 11. Methods
// 12. Interfaces
// 13. Error Handling
// 14. Concurrency with Goroutines
// 15. Channels
// 16. Packages and Modules
// 17. Input and Output
// 18. Testing
// 19. Reflection
// 20. Context

// --------------------------------------------------------------------------------------------
// 1. Introduction to Go
// --------------------------------------------------------------------------------------------

// Go, also known as Golang, is a statically typed, compiled programming language designed by
// Google. It is known for its simplicity, efficiency, and strong support for concurrency. Go
// is particularly well-suited for building scalable and high-performance applications, such
// as web servers, cloud services, and distributed systems.

// Key Features:
// - Simple and concise syntax
// - Fast compilation and execution
// - Built-in support for concurrency (goroutines and channels)
// - Garbage collection for automatic memory management
// - Strong standard library

// Getting Started:
// - Install Go from the official website: https://go.dev/dl/
// - Verify installation by running 'go version' in your terminal.
// - Use 'go run <filename.go>' to run a Go program.
// - Use 'go build <filename.go>' to compile a Go program into an executable.

// --------------------------------------------------------------------------------------------
// 2. Basic Syntax and Structure
// --------------------------------------------------------------------------------------------

// A Go program consists of packages, functions, variables, and statements. The `main` package 
// is the entry point for executables, and the `main` function is where execution begins.

// What a Go Program Consists Of:
// 1. Packages: Organize code into reusable modules. The `main` package is required for 
//    executables.
// 2. Functions: Define reusable blocks of code. The `main` function is mandatory.
// 3. Variables: Store data. Go is statically typed, so types are known at compile time.
// 4. Statements and Expressions: Perform actions and produce values.

// Syntax Highlights:
// - Case Sensitivity: Variable and Function names are case-sensitive.
// - Semicolons: Optional; the compiler inserts them automatically.
// - Curly Braces: `{}` define code blocks.
// - Comments: Use `//` for single-line and `/* ... */` for multi-line comments.

package main

import "fmt"

func main() {
	fmt.Println("Hello, World!")
}

// --------------------------------------------------------------------------------------------
// 3. Data Types and Variables
// --------------------------------------------------------------------------------------------

// Go is statically typed; meaning variable types are determined at compile time.

// Variables can be declared:
// - Using `var`: Explicitly declares a variable with a type.
// - Using `:=`: Infers the type from the assigned value (shorthand syntax).

// Constants can be declared:
// - Using `const`: Declares a constant value.

// Basic data types in Go:
// - `int`, `int8`, `int16`, `int32`, `int64`: Signed integers of varying sizes.
// - `uint`, `uint8`, `uint16`, `uint32`, `uint64`: Unsigned integers of varying sizes.
// - `complex64`, `complex128`: Complex numbers.
// - `float32`, `float64`: Floating-point numbers.
// - `string`: A sequence of characters.
// - `bool`: Boolean values (`true` or `false`).
// - `byte`: Alias for `uint8`, typically used for representing ASCII characters.
// - `rune`: Alias for `int32`, represents a Unicode code point.

// Zero values: 
// - Uninitialized variables have default values (`0` for integers, `""` for strings, `false` for booleans).

// Example:
func main() {
	
	// Explicit declaration with type
	var name string = "David"
	var age int = 30
	var isStudent bool = true

	// Implicit declaration (type inferred)
	height := 5.9       // float64 inferred
    grade := 'A'        // rune (int32) inferred
    salary := 75000.50  // float64 inferred

	const gravity float64 = 9.8

	// - `%d`: Decimal, `%f` Floating point, `%s`: String, `%t`: Boolean, `%c`: Character, `%v`: Default format, 
	// - `%T`: Type of the value, `%b`: Binary, `%x`, `%X`: Hexadecimal, `%%%`: Literal percent sign
	fmt.Println("Name: %s, Age: %d, Student: %t", name, age, isStudent)
	fmt.Println("Height: %f, Grade: %c, Salary: %f", height, grade, salary)
	fmt.Println("Gravity: %f", gravity)
}

// --------------------------------------------------------------------------------------------
// 4. Operators and Expressions
// --------------------------------------------------------------------------------------------

// Operators in Go are used to perform operations on variables and values. Go supports a wide
// range of operators. Expressions are combinations of variables, constants, and operators that
// evaluate to a single value.

// Types of operators in Go:
// - Arithmetic: +, -, *, /, % (addition, subtraction, multiplication, division, modulus).
// - Comparison: ==, !=, >, <, >=, <= (equal, not equal, greater than, less than, etc.).
// - Logical: &&, ||, ! (AND, OR, NOT).
// - Bitwise: &, |, ^, <<, >> (AND, OR, XOR, left shift, right shift).
// - Assignment: =, +=, -=, *=, /=, %=, etc.

func main() {
	a := 10
	b := 5

	// Arithmetic operators
	fmt.Println("Addition:", a+b)     // Output: Addition: 15

	a++ // Increment a
	fmt.Println("Increment:", a)     // Output: Increment: 11

	// Assignment operators
	a += 5
	fmt.Println("Add and assign:", a)   // Output: Add and assign: 16

	// Comparison operators
	fmt.Println("Equal:", a == b)      // Output: Equal: false

	// Logical operators
	x := true
	y := false
	fmt.Println("Logical AND:", x && y) // Output: Logical AND: false

    // Bitwise operators
    c := 60 // Binary: 00111100
    d := 13 // Binary: 00001101
    fmt.Println("Bitwise AND:", c & d) // Output: Bitwise AND: 12 (00001100)
}

// --------------------------------------------------------------------------------------------
// 5. Control Flow Statements
// --------------------------------------------------------------------------------------------

// Control flow statements in Go allow you to control the execution flow of your program based
// on conditions or loops.

// Key Control Flow Statements:
// - `if` and `else`: Used for conditional execution of code blocks.
// - `else if`: Allows chaining multiple conditions.
// - `switch`: Used for multi-way branching based on the value of an expression.
// - `for`: Used for looping, including traditional loops, while-like loops, and infinite loops.
// - `range`: Used to iterate over elements in arrays, slices, maps, strings, and channels.
// - `break` and `continue`: Used to control loop execution.
// - `goto`: Jumps to a labeled statement (use with caution).
// - `defer`: Schedules a function call to be executed after the surrounding function returns.

func main() {
    // if-else statement
    score := 85
    if score >= 90 {
        fmt.Println("Grade: A")
    } else if score >= 80 {
        fmt.Println("Grade: B")
    } else {
        fmt.Println("Grade: C")
    }

    // switch statement
    day := "Monday"
    switch day {
    case "Monday":
        fmt.Println("Start of the workweek.")
    case "Friday":
        fmt.Println("End of the workweek.")
    default:
        fmt.Println("Midweek day.")
    }

    // for loop (traditional)
    for i := 0; i < 5; i++ {
        fmt.Println("Iteration:", i)
    }

    // while-like loop
    j := 0
    for j < 3 {
        fmt.Println("While-like iteration:", j)
        j++
    }

    // infinite loop with break
    k := 0
    for {
        if k >= 2 {
            break
        }
        fmt.Println("Infinite loop iteration:", k)
        k++
    }

    // continue statement
    for i := 0; i < 5; i++ {
        if i == 2 {
            continue // Skip iteration when i is 2
        }
        fmt.Println("Continue example iteration:", i)
    }

	// defer statement
    defer fmt.Println("Deferred statement executed last")
    fmt.Println("This will be printed first")

    // goto statement (use with caution)
    goto myLabel
    fmt.Println("This will be skipped") // This line will be skipped
	myLabel:
    fmt.Println("Jumped to myLabel")
}

// --------------------------------------------------------------------------------------------
// 6. Functions
// --------------------------------------------------------------------------------------------

// Functions in Go are reusable blocks of code that perform a specific task. Functions are a 
// fundamental building block in Go, enabling code modularity, reusability, and readability.

// Key Concepts:
// - Function Declaration: Defined using the `func` keyword, followed by the function name,
//   parameters, return type(s), and the function body.
// - Parameters: Functions can accept input values as parameters.
// - Return Values: Functions can return one or more values. Go supports named return values
//   for clarity.
// - Variadic Functions: Functions that accept a variable number of arguments.
// - Anonymous Functions: Functions without a name, often used as closures.
// - Recursion: Functions can call themselves.

// Function with parameters and a return value
func add(a int, b int) int {
    return a + b
}

// Function with multiple return values
func swap(x, y string) (string, string) {
    return y, x
}

// Function with named return values
func divide(a, b float64) (result float64, err error) {
    if b == 0 {
        err = fmt.Errorf("division by zero")
        return
    }
    result = a / b
    return
}

// Variadic function (accepts a variable number of arguments)
func sum(numbers ...int) int {
    total := 0
    for _, num := range numbers {
        total += num
    }
    return total
}

// Recursive function
func factorial(n int) int {
    if n == 0 {
        return 1
    }
    return n * factorial(n-1)
}

func main() {
    // Calling a function with parameters and return value
    sumResult := add(3, 5)
    fmt.Println("3 + 5 =", sumResult)

    // Calling a function with multiple return values
    a, b := swap("hello", "world")
    fmt.Println("Swapped values:", a, b)

    // Calling a function with named return values
    result, err := divide(10.0, 2.0)
    if err != nil {
        fmt.Println("Error:", err)
    } else {
        fmt.Println("10 / 2 =", result)
    }

    // Calling a variadic function
    total := sum(1, 2, 3, 4, 5)
    fmt.Println("Sum of 1, 2, 3, 4, 5 =", total)

    // Calling a recursive function
    fmt.Println("Factorial of 5 =", factorial(5))

    // Anonymous function (closure)
    double := func(x int) int {
        return x * 2
    }
    fmt.Println("Double of 4 =", double(4))
}

// --------------------------------------------------------------------------------------------
// 7. Arrays and Slices
// --------------------------------------------------------------------------------------------

// Arrays and slices are fundamental data structures in Go used to store collections of elements.
// Arrays have a fixed size, while slices are dynamically sized and more flexible. Slices are
// built on top of arrays and are more commonly used in Go programs due to their versatility.

func main() {
	// Array declaration
	var numbers [5]int // An array of 5 integers
	numbers[0] = 10
	numbers[1] = 20
	numbers[2] = 30
	numbers[3] = 40
	numbers[4] = 50
	fmt.Println("Array:", numbers) // Output: Array: [10 20 30 40 50]

	// Array literal
	colors := [3]string{"red", "green", "blue"}
	fmt.Println("Colors Array:", colors) // Output: Colors Array: [red green blue]

	// Slice declaration using array slicing
	sliceFromArray := numbers[1:4] // Slice from index 1 to 3 (exclusive)
	fmt.Println("sliceFromArray:", sliceFromArray) // Output: sliceFromArray Length: [20 30 40]
	fmt.Println("sliceFromArray Length:", len(sliceFromArray)) // Output: sliceFromArray Length  3
	fmt.Println("sliceFromArray Capacity:", cap(sliceFromArray)) // Output: sliceFromArray Length Capacity: 4

	// Slice creation using make(type, length, capacity)
	makeSlice := make([]int, 3, 5) // Slice with length 3 and capacity 5
	fmt.Println("makeSlice:", makeSlice) // Output: makeSlice: [0 0 0]
	fmt.Println("makeSlice Length:", len(makeSlice)) // Output: makeSlice Length: 3
	fmt.Println("makeSlice Capacity:", cap(makeSlice)) // Output: makeSlice Capacity: 5

	// Appending to a slice
	makeSlice = append(makeSlice, 100)
	fmt.Println("makeSlice after append:", makeSlice) // Output: makeSlice after append: [0 0 0 100]
	fmt.Println("makeSlice Length:", len(makeSlice)) // Output: makeSlice Length: 4
	fmt.Println("makeSlice Capacity:", cap(makeSlice)) // Output: makeSlice Capacity: 5

	// Copying slices
    copySlice := make([]int, len(makeSlice))
    copy(copySlice, makeSlice)
    fmt.Println("copySlice:", copySlice) // Output: copySlice: [0 0 0 100]

	// Iterating over a slice
    for index, value := range copySlice {
        fmt.Printf("Index: %d, Value: %d\n", index, value)
    }
}

// --------------------------------------------------------------------------------------------
// 8. Maps
// --------------------------------------------------------------------------------------------

// Maps in Go are unordered collections of key-value pairs. They are used to store and retrieve
// data efficiently based on unique keys. Maps are similar to dictionaries or hash tables in
// other programming languages. Keys in a map must be of a type that supports equality
// comparison (e.g., integers, strings, structs), while values can be of any type.

func main() {
    // Declaring and initializing a map using a map literal
    person := map[string]string{
        "name": "Alice",
        "age":  "25",
        "city": "New York",
    }
    fmt.Println("Person Map:", person)

    // Accessing values using keys
    fmt.Println("Name:", person["name"])
    fmt.Println("Age:", person["age"])

    // Adding a new key-value pair
    person["occupation"] = "Engineer"
    fmt.Println("Updated Person Map:", person)

    // Updating an existing value
    person["age"] = "26"
    fmt.Println("Updated Age:", person["age"])

    // Deleting a key-value pair
    delete(person, "city")
    fmt.Println("Person Map after deletion:", person)

    // Checking if a key exists
    value, exists := person["city"]
    if exists {
        fmt.Println("City:", value)
    } else {
        fmt.Println("City does not exist in the map.")
    }

    // Iterating over a map
    for key, value := range person {
        fmt.Printf("Key: %s, Value: %s\n", key, value)
    }

    // Declaring and initializing a map using the make function
    scores := make(map[string]int)
    scores["Alice"] = 95
    scores["Bob"] = 85
    scores["Charlie"] = 90
    fmt.Println("Scores Map:", scores)
}

// --------------------------------------------------------------------------------------------
// 9. Structs
// --------------------------------------------------------------------------------------------

// Structs in Go are user-defined types that group together fields of different types. They
// are used to represent complex data structures, similar to classes in object-oriented
// programming. Structs are value types, meaning they are copied when assigned or passed as
// arguments to functions.

// Declaring a struct
type Car struct {
    Make  string
    Model string
    Year  int
    Specs Specs // Nested struct
}

// Declaring another struct
type Specs struct {
    Engine string
    Color  string
}

func main() {
    // Initializing a struct using a struct literal
    car1 := Car{
        Make:  "Toyota",
        Model: "Camry",
        Year:  2020,
        Specs: Specs{
            Engine: "V6",
            Color:  "Red",
        },
    }
    fmt.Println("Car 1:", car1)

    // Accessing struct fields
    fmt.Println("Make:", car1.Make)
    fmt.Println("Model:", car1.Model)
    fmt.Println("Engine:", car1.Specs.Engine)

    // Updating struct fields
    car1.Year = 2021
    car1.Specs.Color = "Blue"
    fmt.Println("Updated Car 1:", car1)

    // Creating a struct instance using the new function
    car2 := new(Car)
    car2.Make = "Honda"
    car2.Model = "Civic"
    car2.Year = 2019
    car2.Specs = Specs{
        Engine: "I4",
        Color:  "Black",
    }
    fmt.Println("Car 2:", *car2)

    // Anonymous struct
    bike := struct {
        Brand string
        Type  string
        Year  int
    }{
        Brand: "Harley-Davidson",
        Type:  "Cruiser",
        Year:  2022,
    }
    fmt.Println("Bike:", bike)

    // Nested structs
    type Showroom struct {
        Name string
        Car  Car
        Location string
    }

    showroom := Showroom{
        Name: "Auto World",
        Car: Car{
            Make:  "Ford",
            Model: "Mustang",
            Year:  2023,
            Specs: Specs{
                Engine: "V8",
                Color:  "Yellow",
            },
        },
        Location: "New York",
    }
    fmt.Println("Showroom:", showroom)
}

// --------------------------------------------------------------------------------------------
// 10. Pointers
// --------------------------------------------------------------------------------------------

// Pointers in Go are variables that store the memory address of another variable. They allow
// you to directly manipulate the memory of a variable, enabling efficient data sharing and
// modification. Pointers are particularly useful for passing large data structures to
// functions without copying the entire structure.

// Key Concepts:
// - Declaration: Pointers are declared using the `*` symbol followed by the type of the
//   variable they point to.
// - Address Operator: The `&` operator is used to get the memory address of a variable.
// - Dereferencing: The `*` operator is used to access the value stored at the memory address
//   a pointer holds.
// - Pointer to Pointer: A pointer can also point to another pointer.
// - Passing Pointers to Functions: Pointers can be passed to functions to modify the original
//   variable.

func main() {
    // Declaring a variable and a pointer
    var num int = 42
    var ptr *int = &num // ptr holds the memory address of num

    fmt.Println("Value of num:", num)
    fmt.Println("Address of num:", ptr)
    fmt.Println("Value at address stored in ptr:", *ptr)

    // Modifying the value using the pointer
    *ptr = 100
    fmt.Println("Updated value of num:", num)

    // Pointer to a pointer
    var ptr2 **int = &ptr
    fmt.Println("Address of ptr:", ptr2)
    fmt.Println("Value at address stored in ptr2:", *ptr2)
    fmt.Println("Value at address stored in *ptr2:", **ptr2)

    // Passing pointers to functions
    increment(&num)
    fmt.Println("Value of num after increment:", num)
}

// Function that accepts a pointer
func increment(p *int) {
    *p++ // Increment the value at the address stored in p
}

// --------------------------------------------------------------------------------------------
// 11. Methods
// --------------------------------------------------------------------------------------------

// Methods in Go are functions that are associated with a specific type, typically a struct.
// They allow you to define behavior for a type, making your code more organized and
// object-oriented. Methods have a receiver, which is a parameter that binds the method to
// the type. The receiver can be either a value or a pointer, depending on whether you want
// to modify the original data or work with a copy.

// Key Concepts:
// - Receiver: The receiver is a special parameter that binds the method to a type. It can be
//   a value receiver (works on a copy) or a pointer receiver (works on the original data).
// - Value Receiver: Methods with value receivers cannot modify the original data.
// - Pointer Receiver: Methods with pointer receivers can modify the original data.
// - Method Declaration: Methods are declared using the `func` keyword, followed by the
//   receiver, method name, parameters, and return type.
// - Method Invocation: Methods are called using the dot (`.`) operator, followed by the
//   method name.

// Declaring a struct
type Car struct {
    Make  string
    Model string
    Year  int
}

// Method with a value receiver
func (c Car) DisplayInfo() {
    fmt.Printf("Make: %s, Model: %s, Year: %d\n", c.Make, c.Model, c.Year)
}

// Method with a pointer receiver
func (c *Car) UpdateYear(newYear int) {
    c.Year = newYear
}

// Method with a value receiver (does not modify the original data)
func (c Car) IsVintage() bool {
    return c.Year < 2000
}

func main() {
    // Creating an instance of Car
    car := Car{
        Make:  "Ford",
        Model: "Mustang",
        Year:  1967,
    }

    // Calling a method with a value receiver
    car.DisplayInfo()

    // Calling a method with a pointer receiver
    car.UpdateYear(1970)
    fmt.Println("Updated Car Info:")
    car.DisplayInfo()

    // Calling a method with a value receiver
    if car.IsVintage() {
        fmt.Println("This car is vintage.")
    } else {
        fmt.Println("This car is not vintage.")
    }
}

// --------------------------------------------------------------------------------------------
// 12. Interfaces
// --------------------------------------------------------------------------------------------

// Interfaces in Go are a powerful feature that define a set of method signatures. They allow
// you to specify behavior without implementing it, enabling polymorphism and flexible code
// design. A type implements an interface by implementing all its methods. Interfaces are
// implicitly satisfied, meaning no explicit declaration is required.

// Key Concepts:
// - Interface Declaration: Interfaces are declared using the `type` and `interface` keywords,
//   followed by a list of method signatures.
// - Implementing Interfaces: A type implements an interface by defining all the methods
//   specified in the interface.
// - Polymorphism: Interfaces allow different types to be treated as the same type if they
//   implement the same interface.
// - Empty Interface: The `interface{}` type is an empty interface that can hold values of any
//   type, similar to `Object` in other languages.
// - Type Assertion: Used to extract the underlying value from an interface.
// - Type Switch: A switch statement that works with types instead of values.

// Declaring an interface
type Vehicle interface {
    Start() string
    Stop() string
}

// Declaring a struct that implements the Vehicle interface
type Car struct {
    Make  string
    Model string
}

// Implementing the Start method for Car
func (c Car) Start() string {
    return fmt.Sprintf("%s %s is starting.", c.Make, c.Model)
}

// Implementing the Stop method for Car
func (c Car) Stop() string {
    return fmt.Sprintf("%s %s is stopping.", c.Make, c.Model)
}

// Declaring another struct that implements the Vehicle interface
type Bike struct {
    Brand string
}

// Implementing the Start method for Bike
func (b Bike) Start() string {
    return fmt.Sprintf("%s bike is starting.", b.Brand)
}

// Implementing the Stop method for Bike
func (b Bike) Stop() string {
    return fmt.Sprintf("%s bike is stopping.", b.Brand)
}

// Function that accepts any type implementing the Vehicle interface
func OperateVehicle(v Vehicle) {
    fmt.Println(v.Start())
    fmt.Println(v.Stop())
}

func main() {
    // Creating instances of Car and Bike
    car := Car{Make: "Toyota", Model: "Camry"}
    bike := Bike{Brand: "Harley-Davidson"}

    // Using the Vehicle interface to operate different vehicles
    OperateVehicle(car)
    OperateVehicle(bike)

    // Type assertion
    var v Vehicle = Car{Make: "Ford", Model: "Mustang"}
    if car, ok := v.(Car); ok {
        fmt.Println("Type assertion successful:", car.Make, car.Model)
    } else {
        fmt.Println("Type assertion failed.")
    }

    // Type switch
    switch v.(type) {
    case Car:
        fmt.Println("The vehicle is a Car.")
    case Bike:
        fmt.Println("The vehicle is a Bike.")
    default:
        fmt.Println("Unknown vehicle type.")
    }

    // Empty interface
    var empty interface{} = 42
    fmt.Println("Empty interface value:", empty)
}

// --------------------------------------------------------------------------------------------
// 13. Error Handling
// --------------------------------------------------------------------------------------------

// Error handling is explicit and relies on returning errors as values from functions.
// Unlike many other languages, Go does not use exceptions or try-catch blocks. Instead,
// errors are treated as regular values, and the caller is responsible for checking and
// handling them. This approach encourages writing robust and predictable code.

// Key Concepts:
// - Errors are represented by the built-in `error` interface.
// - The `error` interface has a single method `Error() string` that returns a string
//   representation of the error.
// - Functions that can fail should return an error as their last return value.
// - Use the `if err != nil` pattern to check for errors.
// - The `fmt.Errorf` function is used to create custom error objects.
// - The `panic` function is used to signal unrecoverable errors (use sparingly).
// - The `recover` function can be used to handle panics (usually in deferred functions).

import (
    "errors"
    "fmt"
)

// Function that returns an error
func divide(a, b float64) (float64, error) {
    if b == 0 {
        // Return an error using the errors package
        return 0, errors.New("division by zero")
    }
    // Return the result and nil for the error
    return a / b, nil
}

// Custom error type
type NegativeNumberError struct {
    Number float64
}

// Implementing the error interface for the custom error type
func (e NegativeNumberError) Error() string {
    return fmt.Sprintf("negative number not allowed: %f", e.Number)
}

// Function that returns a custom error
func sqrt(n float64) (float64, error) {
    if n < 0 {
        // Return a custom error
        return 0, NegativeNumberError{Number: n}
    }
    // Simplified implementation for demonstration
    return n * n, nil
}

func main() {
    // Example 1: Handling errors from a function
    result, err := divide(10, 0)
    if err != nil {
        fmt.Println("Error:", err)
    } else {
        fmt.Println("Result:", result)
    }

    // Example 2: Handling custom errors
    sqrtResult, err := sqrt(-4)
    if err != nil {
        fmt.Println("Error:", err)
    } else {
        fmt.Println("Square Root Result:", sqrtResult)
    }

    // Example 3: Error wrapping
    _, err = divide(10, 0)
    if err != nil {
        // Wrap the error with additional context
        wrappedErr := fmt.Errorf("calculation failed: %w", err)
        fmt.Println("Wrapped Error:", wrappedErr)
    }

    // Example 4: Panic and recover (not recommended for regular error handling)
    defer func() {
        // Recover from a panic and handle it gracefully
        if r := recover(); r != nil {
            fmt.Println("Recovered from panic:", r)
        }
    }()
    // Trigger a panic (simulating an exceptional situation)
    panic("something went wrong")
}

// --------------------------------------------------------------------------------------------
// 14. Concurrency with Goroutines
// --------------------------------------------------------------------------------------------

// Concurrency is achieved using goroutines, which are lightweight threads managed by
// the Go runtime. Goroutines allow you to perform tasks concurrently, making it easier to
// write efficient and scalable programs. Goroutines are cheaper than traditional threads
// and are multiplexed onto a smaller number of OS threads.

// Key Concepts:
// - A goroutine is created by using the `go` keyword followed by a function call.
// - Goroutines run concurrently with the main function and other goroutines.
// - The main function will exit when all non-goroutine functions are done, even if other
//   goroutines are still running.
// - Goroutines communicate with each other primarily through channels (discussed later).
// - Goroutines are non-blocking; the program does not wait for a goroutine to finish before
//   moving on to the next line of code.
// - Use `sync.WaitGroup` to wait for a group of goroutines to finish.

// Important Notes:
// - Goroutines are not threads; they are multiplexed onto threads by the Go runtime.
// - Goroutines are very efficient and can be created in large numbers.

import (
    "fmt"
    "sync"
    "time"
)

// Function to be executed as a goroutine
func printMessage(message string, wg *sync.WaitGroup) {
	defer wg.Done() // Decrement the counter when the goroutine finishes
	for i := 0; i < 5; i++ {
		fmt.Println(message, ":", i)
		time.Sleep(time.Millisecond * 100) // Simulate some work
	}
}

func main() {
	var wg sync.WaitGroup // Create a WaitGroup

	wg.Add(2) // Add 2 to the counter
	go printMessage("First Goroutine", &wg) // Start the first goroutine
	go printMessage("Second Goroutine", &wg) // Start the second goroutine

	fmt.Println("Main function continues...")

	wg.Wait() // Wait for all goroutines to finish
	fmt.Println("Main function finished.")
}

// --------------------------------------------------------------------------------------------
// 15. Channels
// --------------------------------------------------------------------------------------------

// Channels are a typed conduit through which you can send and receive values between goroutines.
// They are a fundamental part of Go's concurrency model, providing a safe way for goroutines
// to communicate and synchronize.

// Key Concepts:
// - Channels are created using the `make` function with the `chan` keyword, followed by the
//   data type that the channel will carry.
// - Channels can be buffered or unbuffered.
// - Unbuffered channels require both a sender and a receiver to be ready before a transfer
//   can occur (synchronous).
// - Buffered channels have a capacity; a sender can send values until the buffer is full,
//   and a receiver can receive values until the buffer is empty (asynchronous).
// - The `<-` operator is used to send and receive values on a channel.
// - Sending to a closed channel will cause a panic.
// - Receiving from a closed channel will return the zero value of the channel's type and false.
// - The `close` function is used to close a channel (only the sender should close a channel).
// - The `range` keyword can be used to iterate over a channel until it is closed.

import "fmt"
import "time"

// Function to send data on a channel
func sendData(ch chan int) {
	for i := 0; i < 5; i++ {
		ch <- i // Send data to the channel
		fmt.Println("Sent:", i)
		time.Sleep(time.Millisecond * 100)
	}
    close(ch) // Close the channel after sending all data
}

// Function to receive data from a channel
func receiveData(ch chan int) {
    for value := range ch { // Receive data from the channel until it is closed
        fmt.Println("Received:", value)
    }
}

func main() {
	// Create an unbuffered channel
	ch := make(chan int)

	// Start goroutines to send and receive data
	go sendData(ch)
	go receiveData(ch)

	// Wait for the goroutines to finish
	time.Sleep(time.Second * 2)
	fmt.Println("Main function finished.")

    // Buffered Channel
    bufferedCh := make(chan string, 2)
    bufferedCh <- "First"
    bufferedCh <- "Second"
    fmt.Println("Buffered channel length:", len(bufferedCh)) // Output: Buffered channel length: 2
    fmt.Println("Received:", <-bufferedCh) // Output: Received: First
    fmt.Println("Received:", <-bufferedCh) // Output: Received: Second
    close(bufferedCh)

    // Receiving from closed channel
    val, ok := <-bufferedCh
    fmt.Println("Received from closed channel:", val, ok) // Output: Received from closed channel:  false
}

// --------------------------------------------------------------------------------------------
// 16. Packages and Modules
// --------------------------------------------------------------------------------------------

// Packages and modules are fundamental for organizing and reusing Go code.

// Packages:
// - A package is a collection of source files in the same directory that are compiled together.
// - Packages provide namespaces for code, preventing naming conflicts.
// - Package names are typically the same as the directory they reside in.
// - The `main` package is special; it's the entry point for executable programs.
// - Packages can be imported into other packages using the `import` statement.
// - Exported identifiers (functions, types, variables, etc.) start with a capital letter.
// - Unexported identifiers start with a lowercase letter and are only visible within the package.

// Modules:
// - A module is a collection of related Go packages.
// - Modules are used for versioning and managing dependencies.
// - A module is defined by a `go.mod` file in the root directory of the module.
// - The `go.mod` file specifies the module's import path, Go version, and dependencies.
// - Modules are used to manage external libraries.
// - The `go get` command is used to download and install module dependencies.

// Key Concepts:
// - Packages provide code organization; modules provide dependency management.
// - Modules are the modern way to manage Go projects.
// - Use `go mod init <module-name>` to create a new module.
// - Use `go mod tidy` to automatically add and remove dependencies.
// - Use `go build` to compile code within a module.


// mypackage/mypackage.go
package mypackage

import "fmt"

// Exported function
func Greet(name string) {
    fmt.Println("Hello, ", name, " from mypackage!")
}

// Unexported function
func internalMessage() {
    fmt.Println("This is an internal message from mypackage.")
}

// main.go
package main

import (
	"fmt"
    "myproject/mypackage" // Import the custom package
)

func main() {
	fmt.Println("This is the main package.")
    mypackage.Greet("Go User") // Call the exported function from mypackage
    // mypackage.internalMessage() // This will cause a compile error, internalMessage is not exported.
}

// --------------------------------------------------------------------------------------------
// 17. Input and Output
// --------------------------------------------------------------------------------------------

// Input and output (I/O) in Go are handled using the `fmt`, `os`, and `bufio` packages, among
// others.

// Key Concepts:
// - Console I/O: The `fmt` package provides functions like `Println`, `Scan`, and `Scanln` for
//   reading from and writing to the console.
// - File I/O: The `os` package provides functions for opening, reading, writing, and closing
//   files.
// - Buffered I/O: The `bufio` package provides buffered I/O operations, which can improve
//   performance for large data streams.
// - Interfaces: Go's I/O is based on interfaces like `io.Reader` and `io.Writer`, allowing
//   for flexible and reusable code.

package main

import (
    "bufio"
    "fmt"
    "os"
)

func main() {
    // Console Output
    fmt.Println("Hello, World!")

    // Console Input
    var name string
    fmt.Print("Enter your name: ")
    fmt.Scanln(&name)
    fmt.Println("Hello,", name)

    // File I/O: Writing to a file
    file, err := os.Create("output.txt")
    if err != nil {
        fmt.Println("Error creating file:", err)
        return
    }
    defer file.Close()

    writer := bufio.NewWriter(file)
    _, err = writer.WriteString("Hello, File!\n")
    if err != nil {
        fmt.Println("Error writing to file:", err)
        return
    }
    writer.Flush()

    // File I/O: Reading from a file
    file, err = os.Open("output.txt")
    if err != nil {
        fmt.Println("Error opening file:", err)
        return
    }
    defer file.Close()

    scanner := bufio.NewScanner(file)
    for scanner.Scan() {
        fmt.Println("Read from file:", scanner.Text())
    }

    if err := scanner.Err(); err != nil {
        fmt.Println("Error reading file:", err)
    }
}

// --------------------------------------------------------------------------------------------
// 18. Testing
// --------------------------------------------------------------------------------------------

// Testing in Go is built into the language through the `testing` package. Go encourages writing
// tests alongside your code, and the `go test` command is used to run tests. Tests are written
// in files ending with `_test.go` and use functions prefixed with `Test` to define test cases.
// Go also supports benchmarks and example tests.

// Key Concepts:
// - Test Functions: Test functions are defined with the prefix `Test` and take a `*testing.T`
//   parameter. They use methods like `Error`, `Fail`, and `Log` to report test failures.
// - Table-Driven Tests: A common pattern where multiple test cases are defined in a slice and
//   iterated over in a single test function.
// - Benchmarks: Benchmark functions are defined with the prefix `Benchmark` and take a
//   `*testing.B` parameter. They measure the performance of code.
// - Example Tests: Example functions are defined with the prefix `Example` and are used to
//   demonstrate how to use a package or function.
// - Test Coverage: The `go test` command can generate coverage reports to show how much of
//   your code is tested.

// File: mymodule/mymath/mymath.go
// package mymath
package mymath

// Function to be tested
func Add(a int, b int) int {
	return a + b
}

// File: mymodule/mymath/mymath_test.go
// package mymath
package mymath

import "testing"

// Test function for Add
func TestAdd(t *testing.T) {
	result := Add(2, 3)
	if result != 5 {
		t.Errorf("Add(2, 3) = %d; want 5", result)
	}
}

// Test function for Add with table-driven tests
func TestAddTable(t *testing.T) {
	testCases := []struct {
		a    int
		b    int
		want int
	}{
		{2, 3, 5},
		{-1, 1, 0},
		{0, 0, 0},
		{10, -5, 5},
	}

	for _, tc := range testCases {
		result := Add(tc.a, tc.b)
		if result != tc.want {
			t.Errorf("Add(%d, %d) = %d; want %d", tc.a, tc.b, result, tc.want)
		}
	}
}

// Benchmark function for Add
func BenchmarkAdd(b *testing.B) {
    for i := 0; i < b.N; i++ {
        Add(10, 20)
    }
}

// --------------------------------------------------------------------------------------------
// 19. Reflection
// --------------------------------------------------------------------------------------------

// Reflection is provided by the `reflect` package, which allows you to inspect and manipulate
// the types and values of variables at runtime. Reflection is a powerful feature but should be
// used sparingly, as it can make code harder to understand and less efficient.

// Key Concepts:
// - `reflect.TypeOf`: Represents the type of a variable.
// - `reflect.ValueOf`: Represents the value of a variable.
// - Type Inspection: You can inspect the type of a variable, including its kind (e.g., int,
//   string, struct) and methods.
// - Value Manipulation: You can modify the value of a variable using reflection, but this
//   requires care to avoid runtime errors.
// - Struct Tags: Reflection is often used to work with struct tags, which are metadata
//   associated with struct fields.

import (
    "fmt"
    "reflect"
)

// Struct with tags
type Person struct {
    Name string `json:"name"`
    Age  int    `json:"age"`
}

func main() {
    // Inspecting a variable's type and value
    x := 42
    fmt.Println("Type of x:", reflect.TypeOf(x))
    fmt.Println("Value of x:", reflect.ValueOf(x))

    // Inspecting a struct
    p := Person{Name: "Alice", Age: 25}
    t := reflect.TypeOf(p)
    v := reflect.ValueOf(p)

    // Iterating over struct fields
    for i := 0; i < t.NumField(); i++ {
        field := t.Field(i)
        value := v.Field(i)
        fmt.Printf("Field: %s, Type: %s, Value: %v, Tag: %s\n",
            field.Name, field.Type, value.Interface(), field.Tag.Get("json"))
    }

    // Modifying a value using reflection (requires a pointer)
    y := 10
    v2 := reflect.ValueOf(&y).Elem() // Get the addressable value
    v2.SetInt(20)                    // Modify the value
    fmt.Println("Modified value of y:", y)
}

// --------------------------------------------------------------------------------------------
// 20. Context
// --------------------------------------------------------------------------------------------

// The `context` package provides a way to manage the lifecycle of operations, such as
// cancelation, timeouts, and deadlines, across API boundaries and between goroutines. It is
// commonly used in servers, clients, and other systems where operations need to be canceled
// or have a timeout.

// Key Concepts:
// - `context.Context`: The core type that carries deadlines, cancelation signals, and other
//   request-scoped values across API boundaries and between goroutines.
// - Cancelation: A `context.Context` can be canceled using the `context.WithCancel`,
//   which returns a cancel function to signal that the operation should be stopped.
// - Timeouts: A `context.Context` can have a timeout using the `context.WithTimeout`,
//   which automatically cancels the context after a specified duration.
// - Deadlines: A `context.Context` can have a deadline using the `context.WithDeadline`
//   which cancels the context at a specific time.
// - Values: A `context.Context` can carry request-scoped values using the `context.WithValue`.

import (
    "context"
    "fmt"
    "time"
)

func operation(ctx context.Context) {
    select {
    case <-time.After(500 * time.Millisecond):
        fmt.Println("Operation completed.")
    case <-ctx.Done():
        fmt.Println("Operation canceled:", ctx.Err())
    }
}

func main() {
    // Context with cancelation
    ctx, cancel := context.WithCancel(context.Background())
    go operation(ctx)
    time.Sleep(200 * time.Millisecond)
    cancel() // Cancel the operation
    time.Sleep(100 * time.Millisecond)

    // Context with timeout
    ctx, cancel = context.WithTimeout(context.Background(), 300*time.Millisecond)
    defer cancel()
    go operation(ctx)
    time.Sleep(500 * time.Millisecond)

    // Context with deadline
    deadline := time.Now().Add(400 * time.Millisecond)
    ctx, cancel = context.WithDeadline(context.Background(), deadline)
    defer cancel()
    go operation(ctx)
    time.Sleep(500 * time.Millisecond)

    // Context with values
    ctx = context.WithValue(context.Background(), "key", "value")
    fmt.Println("Value from context:", ctx.Value("key"))
}

// Happy Coding!
Licensed under
CC BY-SA 4.0