- Package Declaration
- Import Package
- Function
- Statements and Expressions
package main //this program belongs to main package
import("fmt") // import fomrat predefined library and its function
func main(){
fmt.println("Hello World !"); // function from the package
}
- Save the file as .go
- VS Code > Terminal > go run .\hello.go
- VS Code > Terminal> go build .\hello.go
// Single Line Comment
/* Multi Line Comment */
- No Error
func main(){
fmt.println("Hello World !");
}
- Throws Error { should not start
func main()
{
fmt.println("Hello World !");
}
Data Type | Explanation |
---|---|
int8 | 8-bit signed integer |
int16 | 16-bit signed integer |
int32 | 32-bit signed integer |
int64 | 64-bit signed integer |
uint8 | 8-bit unsigned integer |
uint16 | 16-bit unsigned integer |
uint32 | 32-bit unsigned integer |
uint64 | 64-bit unsigned integer |
int | Both int and uint contain same size, either 32 or 64 bit. |
uint | Both int and uint contain same size, either 32 or 64 bit. |
rune | It is a synonym of int32 and also represent Unicode code points. |
byte | It is a synonym of uint8. |
uintptr | It is an unsigned integer type. Its width is not defined, but its can hold all the bits of a pointer value. |
package main
import "fmt"
func main() {
var a int8 = -128 // 8-bit signed integer
var b int16 = -32768 // 16-bit signed integer
var c int32 = -2147483648 // 32-bit signed integer
var d int64 = -9223372036854775808 // 64-bit signed integer
fmt.Println(a, b, c, d) // Output: -128 -32768 -2147483648 -9223372036854775808
}
package main
import "fmt"
func main() {
var e uint8 = 255 // 8-bit unsigned integer
var f uint16 = 65535 // 16-bit unsigned integer
var g uint32 = 4294967295 // 32-bit unsigned integer
var h uint64 = 18446744073709551615 // 64-bit unsigned integer
fmt.Println(e, f, g, h) // Output: 255 65535 4294967295 18446744073709551615
}
package main
import "fmt"
func main() {
var i int = -42 // 32-bit or 64-bit signed integer (depends on architecture)
var j uint = 42 // 32-bit or 64-bit unsigned integer (depends on architecture)
fmt.Println(i, j) // Output depends on architecture: -42 42
}
package main
import "fmt"
func main() {
var r rune = 'A' // Unicode code point (int32)
fmt.Printf("Rune: %c, Unicode: %U\n", r, r) // Output: Rune: A, Unicode: U+0041
}
package main
import "fmt"
func main() {
var b byte = 'A' // Same as uint8
fmt.Printf("Byte: %c, Value: %d\n", b, b) // Output: Byte: A, Value: 65
}
package main
import "fmt"
func main() {
var x int = 100
var ptr uintptr = uintptr(&x) // Converts pointer to uintptr
fmt.Printf("Pointer: %x\n", ptr) // Output: Hexadecimal memory address
}
- Must start with a letter or underscore (
_
). - Cannot start with a digit.
- Can contain alphanumeric characters (
A-Z
,a-z
,0-9
) or an underscore (_
). - Go is case-sensitive (e.g.,
age
andAge
are different variables). - There is no limit on the length of a variable name.
- Variable names cannot contain spaces.
- Keywords cannot be used as variable names.
var variableName type = value;
-
Explicit Type Declaration
var a string = "Isaac" // Declaring the type explicitly as string
-
Type Inference
var a string = "Isaac" var b = "Isaac" // Type is inferred by the compiler (string in this case)
-
Declaration Without Initialization
var a string // Declaring the variable, no value assigned yet a = "J" // Assigning value later
-
In Block
var( a int b int=1 c string="Hello" )
-
Multiple Declaration
var a,b = 6,"hello" var a,b int = 1,3
In Go, the :=
syntax is used for short variable declaration, allowing you to both declare and initialize a variable in a concise way. It is primarily used when you want the Go compiler to infer the variable's type based on the value assigned. Here are the key scenarios and rules for using :=
:
-
Inside Functions:
:=
can only be used within function bodies (local scope). It cannot be used for declaring variables at the package level (outside functions).func main() { name := "Isaac" // Compiler infers the type as string }
-
Type Inference: When you want the compiler to infer the type of the variable based on the assigned value.
func main() { x := 42 // Compiler infers the type as int pi := 3.14 // Compiler infers the type as float64 isActive := true // Compiler infers the type as bool }
-
Multiple Variable Declaration: You can use
:=
to declare and initialize multiple variables at once.func main() { a, b := 10, "hello" // a is int, b is string }
-
Reassigning at Least One Variable: If one or more variables in a multi-variable assignment are already declared, you can still use
:=
to redeclare and assign new values to the others.func main() { x := 5 x, y := 10, 20 // x is reassigned to 10, y is newly declared as 20 }
-
Outside Functions:
:=
cannot be used at the package level (outside a function). Usevar
for global variables.var globalVar = "Outside function" // Correct way to declare at package level
-
When You Need a Specific Type: If you want to explicitly declare the type of a variable, use the
var
keyword instead of:=
.var num int = 42 // Declaring a variable with a specific type
package main
import "fmt"
func main() {
name := "Alice" // Compiler infers the type as string
age := 30 // Compiler infers the type as int
isStudent := false // Compiler infers the type as bool
fmt.Println(name, age, isStudent) // Output: Alice 30 false
}
In summary, use :=
for local variable declarations inside functions when you want concise code and type inference. Use the var
keyword when you need to declare a variable globally or specify its type explicitly.
Constants in Go are fixed values that cannot be changed once declared.
- Constants are declared using the
const
keyword, similar to variables. - By convention, constant names are often written in uppercase.
- Constants can be declared both inside and outside of functions.
- The type of the constant is explicitly declared.
const A int = 1
- The type of the constant is inferred by the compiler.
const A = 1
- You can declare multiple constants in a block.
const ( A int = 1 B int = 2 )
package main
import "fmt"
// Global constants
const (
PI float64 = 3.14
E = 2.71 // Untyped
)
func main() {
const GREETING = "Hello, Go!" // Constant inside a function
fmt.Println(PI) // Output: 3.14
fmt.Println(E) // Output: 2.71
fmt.Println(GREETING) // Output: Hello, Go!
}
Go provides several functions for printing and formatting, primarily from the fmt
package. Commonly used functions include:
fmt.Print()
: Prints the text without formatting.fmt.Println()
: Prints the text followed by a newline.fmt.Printf()
: Prints formatted text.
Go uses formatting verbs for printing variables in specific formats using fmt.Printf()
.
Here’s a more complete list of the Go formatting verbs, with their explanations and examples:
Verb | Description | Example Output |
---|---|---|
%v |
Default format for the value | fmt.Printf("%v", 42) -> 42 |
%+v |
Prints struct with field names | fmt.Printf("%+v", myStruct) -> {Field1: 10 Field2: 20} |
%#v |
Go-syntax representation | fmt.Printf("%#v", myStruct) -> main.MyStruct{Field1: 10, Field2: 20} |
%T |
Type of the value | fmt.Printf("%T", 42) -> int |
%% |
Literal percent sign | fmt.Printf("%%") -> % |
Verb | Description | Example Output |
---|---|---|
%d |
Decimal integer | fmt.Printf("%d", 42) -> 42 |
%b |
Binary format | fmt.Printf("%b", 42) -> 101010 |
%o |
Octal format | fmt.Printf("%o", 42) -> 52 |
%x |
Hexadecimal (lowercase) | fmt.Printf("%x", 42) -> 2a |
%X |
Hexadecimal (uppercase) | fmt.Printf("%X", 42) -> 2A |
%c |
Character (Unicode) | fmt.Printf("%c", 65) -> A |
%U |
Unicode format (character + code point) | fmt.Printf("%U", 65) -> U+0041 |
%q |
Single-quoted character | fmt.Printf("%q", 65) -> 'A' |
Verb | Description | Example Output |
---|---|---|
%f |
Decimal point but no exponent | fmt.Printf("%f", 3.14) -> 3.140000 |
%.2f |
Decimal point, with precision | fmt.Printf("%.2f", 3.14) -> 3.14 |
%e |
Scientific notation (lowercase) | fmt.Printf("%e", 3.14) -> 3.140000e+00 |
%E |
Scientific notation (uppercase) | fmt.Printf("%E", 3.14) -> 3.140000E+00 |
%g |
Compact representation | fmt.Printf("%g", 3.14) -> 3.14 |
%G |
Compact representation (uppercase) | fmt.Printf("%G", 3.14) -> 3.14 |
%x |
Hexadecimal notation (with fraction) | fmt.Printf("%x", 3.14) -> 0x1.91eb851eb851fp+1 |
%p |
Pointer | fmt.Printf("%p", &a) -> 0x123456 |
Verb | Description | Example Output |
---|---|---|
%s |
String | fmt.Printf("%s", "hello") -> hello |
%q |
Double-quoted string with escaped characters | fmt.Printf("%q", "hello") -> "hello" |
%x |
Hex dump of string (lowercase) | fmt.Printf("%x", "abc") -> 616263 |
%X |
Hex dump of string (uppercase) | fmt.Printf("%X", "abc") -> 616263 |
%p |
Pointer | fmt.Printf("%p", &str) -> 0x123456 |
Verb | Description | Example Output |
---|---|---|
%t |
Boolean (true/false) | fmt.Printf("%t", true) -> true |
Verb | Description | Example Output |
---|---|---|
%p |
Pointer address | fmt.Printf("%p", &a) -> 0x123456 |
Verb | Description | Example Output |
---|---|---|
%5d |
Pad integer with spaces (right-aligned) | fmt.Printf("%5d", 42) -> 42 |
%-5d |
Pad integer with spaces (left-aligned) | fmt.Printf("%-5d", 42) -> 42 |
%05d |
Pad integer with zeros (right-aligned) | fmt.Printf("%05d", 42) -> 00042 |
%7.2f |
Width and precision for floating-point | fmt.Printf("%7.2f", 3.14) -> 3.14 |
package main
import "fmt"
func main() {
// General Formatting
name := "Go"
age := 10
pi := 3.14159
fmt.Printf("Name: %s, Age: %d, Pi: %.2f\n", name, age, pi) // Name: Go, Age: 10, Pi: 3.14
fmt.Printf("Binary Age: %b, Hex Age: %x\n", age, age) // Binary Age: 1010, Hex Age: a
fmt.Printf("Type of pi: %T\n", pi) // Type of pi: float64
// String and Unicode formatting
fmt.Printf("Quoted: %q\n", "Hello, Go!") // Quoted: "Hello, Go!"
fmt.Printf("Character: %c\n", 65) // Character: A
// Width and Precision
fmt.Printf("Padded integer: %5d\n", 42) // Padded integer: 42
fmt.Printf("Padded float: %7.2f\n", pi) // Padded float: 3.14
}
Here is a comprehensive list of all Go language operators in markdown format:
Operator | Description | Example |
---|---|---|
+ |
Addition | x + y (sum of x and y ) |
- |
Subtraction | x - y (difference between x and y ) |
* |
Multiplication | x * y (product of x and y ) |
/ |
Division | x / y (quotient of x divided by y ) |
% |
Modulus (remainder) | x % y (remainder of x divided by y ) |
Operator | Description | Example |
---|---|---|
== |
Equal to | x == y (checks if x is equal to y ) |
!= |
Not equal to | x != y (checks if x is not equal to y ) |
> |
Greater than | x > y (checks if x is greater than y ) |
< |
Less than | x < y (checks if x is less than y ) |
>= |
Greater than or equal to | x >= y (checks if x is greater than or equal to y ) |
<= |
Less than or equal to | x <= y (checks if x is less than or equal to y ) |
Operator | Description | Example |
---|---|---|
&& |
Logical AND | x && y (true if both x and y are true) |
|| | Logical OR | x || y (true if either x or y is true) |
! |
Logical NOT | !x (true if x is false) |
Operator | Description | Example |
---|---|---|
& |
Bitwise AND | x & y (bitwise AND of x and y ) |
| | Bitwise OR | x | y (bitwise OR of x and y ) |
^ |
Bitwise XOR | x ^ y (bitwise XOR of x and y ) |
&^ |
Bit Clear (AND NOT) | x &^ y (bitwise AND NOT of x and y ) |
<< |
Left shift | x << n (shift x left by n bits) |
>> |
Right shift | x >> n (shift x right by n bits) |
Operator | Description | Example |
---|---|---|
= |
Assign | x = y (assigns y to x ) |
+= |
Add and assign | x += y (adds y to x and assigns to x ) |
-= |
Subtract and assign | x -= y (subtracts y from x and assigns to x ) |
*= |
Multiply and assign | x *= y (multiplies x by y and assigns to x ) |
/= |
Divide and assign | x /= y (divides x by y and assigns to x ) |
%= |
Modulus and assign | x %= y (assigns remainder of x divided by y to x ) |
<<= |
Left shift and assign | x <<= n (left shifts x by n bits and assigns to x ) |
>>= |
Right shift and assign | x >>= n (right shifts x by n bits and assigns to x ) |
&= |
Bitwise AND and assign | x &= y (bitwise AND x with y and assigns to x ) |
|= |
Bitwise OR and assign | x |= y (bitwise OR x with y and assigns to x ) |
^= |
Bitwise XOR and assign | x ^= y (bitwise XOR x with y and assigns to x ) |
&^= |
Bit clear (AND NOT) and assign | x &^= y (bitwise AND NOT x with y and assigns to x ) |
Operator | Description | Example |
---|---|---|
& |
Address of | &x (returns the memory address of x ) |
* |
Pointer dereference | *p (dereferences pointer p ) |
... |
Variadic arguments | func(args ...int) (function accepts a variable number of arguments) |
Operator | Description | Example |
---|---|---|
++ |
Increment | x++ (increments x by 1) |
-- |
Decrement | x-- (decrements x by 1) |
*
/
%
<<
>>
&
&^
+
-
|
^
==
!=
<
<=
>
>=
&&
||
This list covers all major operators in Go, including arithmetic, relational, logical, bitwise, assignment, and miscellaneous operators, as well as their examples and explanations.
Here’s an expanded version including range
and multi-value switch
statements in Go:
The if
statement is used to execute code based on conditions, with optional else
and else if
blocks.
if condition {
// Code when condition is true
} else {
// Code when condition is false
}
A cleaner alternative to multiple if-else
conditions, switch
checks multiple cases.
switch variable {
case value1:
// Code when variable == value1
case value2:
// Code when variable == value2
default:
// Code if no cases match
}
In Go, you can match multiple values in a single case
.
switch day {
case "Saturday", "Sunday":
fmt.Println("It's the weekend!")
default:
fmt.Println("It's a weekday.")
}
Switch cases can evaluate expressions.
switch {
case x < 0:
fmt.Println("Negative")
case x == 0:
fmt.Println("Zero")
default:
fmt.Println("Positive")
}
The only looping construct in Go.
for initializer; condition; post {
// Code to execute in each iteration
}
for condition {
// Loop runs while condition is true
}
for {
// Infinite loop
}
The range
keyword is used in loops to iterate over elements of arrays, slices, maps, and channels.
numbers := []int{1, 2, 3, 4, 5}
for i, num := range numbers {
fmt.Printf("Index: %d, Value: %d\n", i, num)
}
i
is the index andnum
is the value at that index.- If you don't need the index, use
_
:
for _, num := range numbers {
fmt.Println(num)
}
person := map[string]string{"name": "Alice", "city": "Wonderland"}
for key, value := range person {
fmt.Printf("%s: %s\n", key, value)
}
When iterating over a string, range
returns the index and the rune (Unicode code point).
for i, r := range "GoLang" {
fmt.Printf("Index: %d, Rune: %c\n", i, r)
}
Exits from the current loop or switch.
for i := 0; i < 5; i++ {
if i == 3 {
break // Exits the loop when i == 3
}
fmt.Println(i)
}
Skips the remaining code in the current iteration of the loop and moves to the next iteration.
for i := 0; i < 5; i++ {
if i%2 == 0 {
continue // Skip even numbers
}
fmt.Println(i)
}
Jumps to a labeled statement. Use sparingly, as it can lead to complex, unreadable code.
func main() {
x := 10
if x > 5 {
goto Label
}
fmt.Println("This won't execute")
Label:
fmt.Println("Jumped to label!")
}
Schedules a function call to run after the surrounding function completes.
func main() {
defer fmt.Println("This will run last.")
fmt.Println("This will run first.")
}
- Deferred function calls are executed in LIFO (Last In, First Out) order.
panic
: Stops normal execution and begins panicking.recover
: Regains control of a panicking goroutine.
package main
import "fmt"
func handlePanic() {
// detect if panic occurs or not
a := recover()
if a != nil {
fmt.Println("RECOVER", a)
}
}
func division(num1, num2 int) {
// execute the handlePanic even after panic occurs
defer handlePanic()
// if num2 is 0, program is terminated due to panic
if num2 == 0 {
panic("Cannot divide a number by zero")
} else {
result := num1 / num2
fmt.Println("Result: ", result)
}
}
func main() {
division(4, 2)
division(8, 0)
division(2, 8)
}
- Result: 2
- RECOVER Cannot divide a number by zero
- Result: 0
eventhough 45 is < 100, it only goes to second case, to address this, use fallthrough
func main() {
i := 45
switch {
case i < 10:
fmt.Println("i is less than 10")
case i < 50:
fmt.Println("i is less than 50")
case i < 100:
fmt.Println("i is less than 100")
}
}
In Go, switch cases do not fall through by default. You must use the fallthrough
keyword to explicitly continue to the next case.
package main
import "fmt"
func main() {
i := 45
switch {
case i < 10:
fmt.Println("i is less than 10")
fallthrough
case i < 50:
fmt.Println("i is less than 50")
fallthrough
fmt.Println("Not allowed")
case i < 100:
fmt.Println("i is less than 100")
}
}
Switch can include a short statement before evaluating the cases.
switch x := 10; {
case x > 0:
fmt.Println("x is positive")
case x < 0:
fmt.Println("x is negative")
default:
fmt.Println("x is zero")
}
// Package declaration
package main
// Import the fmt package for formatted I/O operations
import (
"fmt"
)
// Defining a simple function f1
func f1() {
// Currently empty function
}
// Main function where the execution starts
func main() {
f1() // Calling f1 function
}
// Define a function myFunction that takes two integers as arguments
// and returns their sum.
func myFunction(x int, y int) int {
return x + y // Returning the sum of x and y
}
func main() {
// Calling myFunction with 1 and 2 as arguments, and printing the result
fmt.Println(myFunction(1, 2)) // Output: 3
}
// This example shows how to use a named return value.
// In this case, result is an int, and we assign it within the function.
func myFunction(x int, y int) (result int) {
result = x + y // Assigning result the value of x + y
return // The return statement will return the value of result
}
func main() {
// The function will still return the sum of 1 and 2
fmt.Println(myFunction(1, 2)) // Output: 3
}
// This function returns two values: an integer and a string.
// It doubles the integer and appends "World!" to the string.
func myFunction(x int, y string) (result int, txt1 string) {
result = x + x // result is double the value of x
txt1 = y + " World!" // txt1 is y appended with " World!"
return // Return both result and txt1
}
func main() {
// The function returns two values, so both need to be captured
fmt.Println(myFunction(5, "Hello")) // Output: (10, "Hello World!")
}
// This function still returns two values, but the caller chooses to ignore the first return value.
func myFunction(x int, y string) (result int, txt1 string) {
result = x + x // result is double the value of x
txt1 = y + " World!" // txt1 is y appended with " World!"
return // Return both result and txt1
}
func main() {
// Here, the first return value (result) is omitted by using "_".
_, b := myFunction(5, "Hello") // Capture only the second return value (txt1)
fmt.Println(b) // Output: "Hello World!"
}
// A recursive function that prints numbers from x up to 10.
// The recursion stops when x equals 11.
func testcount(x int) int {
if x == 11 {
return 0 // Base case: if x is 11, stop the recursion
}
fmt.Println(x) // Print the current value of x
return testcount(x + 1) // Recursive call with x incremented by 1
}
func main() {
// Start recursion from 1
testcount(1) // Output: 1 2 3 4 5 6 7 8 9 10
}
In Go, a variadic function is one that can accept a variable number of arguments. You define a variadic parameter by using ...
before the type. This allows you to pass zero or more arguments of the specified type.
package main
import "fmt"
// Variadic function that sums up integers
func sum(nums ...int) int {
total := 0
for _, num := range nums {
total += num
}
return total
}
func main() {
result := sum(1, 2, 3, 4, 5)
fmt.Println("Sum:", result) // Output: Sum: 15
}
func sum(nums ...int) int
defines a functionsum
that takes a variadic parameternums
of typeint
.- Inside the function,
nums
is treated like a slice ([]int
), so you can use range-based loops to iterate over the elements. - When calling
sum
, you can pass any number of integers.
Anonymous functions, or lambda functions, are functions defined without a name. They can be used inline where they are needed, and they are often used for short-lived operations or callbacks.
package main
import "fmt"
func main() {
// Anonymous function assigned to a variable
add := func(a, b int) int {
return a + b
}
result := add(10, 20)
fmt.Println("Sum:", result) // Output: Sum: 30
// Anonymous function used immediately
result = func(a, b int) int {
return a * b
}(5, 6)
fmt.Println("Product:", result) // Output: Product: 30
}
add := func(a, b int) int { ... }
creates an anonymous function and assigns it to the variableadd
.- This function can then be invoked like a regular function.
- You can also define and call an anonymous function immediately, as shown in the second part of the example.
In Go, the init
function is a special function that is executed automatically when a package is imported. It is used for initialization tasks that need to be completed before the main
function starts executing. The init
function does not take any arguments and does not return any values.
- Execution Order: The
init
function is executed before themain
function. - Multiple
init
Functions: You can have multipleinit
functions in a single file, and they are executed in the order they appear. - Multiple Packages: When dealing with multiple packages, the
init
functions are executed in a specific order based on the package import hierarchy.
In this example, we demonstrate how the init
function initializes variables before the main
function is executed.
package main
import "fmt"
// Package-level variables
var greetings string
var age int
// Init function
func init() {
fmt.Println("I always execute before main() function")
greetings = "Hello world"
}
// Main function
func main() {
fmt.Println("I execute after init() function")
fmt.Println(greetings)
fmt.Printf("Go language is %d years old \n", age)
}
I always execute before main() function
I execute after init() function
Hello world
Go language is 0 years old
- The
init
function sets thegreetings
variable beforemain
executes. - The
main
function prints the values initialized byinit
, demonstrating thatinit
executes first.
You can define multiple init
functions within the same file. They are executed in the order they are defined.
package main
import "fmt"
// First init function
func init() {
fmt.Println("<<< First >>>")
}
// Second init function
func init() {
fmt.Println("<<< Second >>>")
}
// Third init function
func init() {
fmt.Println("<<< Third >>>")
}
// Main function
func main() {
fmt.Println("I execute after init() functions")
}
<<< First >>>
<<< Second >>>
<<< Third >>>
I execute after init() functions
- The
init
functions are executed in the order they appear in the file. - The
main
function runs after allinit
functions have completed.
When working with multiple packages, init
functions in different packages are executed in a specific order based on import dependencies.
// File: a/a.go
package a
func init() {
println("init() function in a/a.go")
}
func Greetings() {
println("Hello, world from a/a.go")
}
// File: b/b.go
package b
import "fmt"
func init() {
fmt.Println("init() function in b/b.go")
}
func Greetings() {
fmt.Println("Hello, world from b/b.go")
}
// File: main.go
package main
import (
"fmt"
"a" // import package a
"b" // import package b
)
func init() {
fmt.Println("init() function in main.go")
}
func main() {
fmt.Println("Executing main() function in main.go")
a.Greetings()
b.Greetings()
}
init() function in a/a.go
init() function in b/b.go
init() function in main.go
Executing main() function in main.go
Hello, world from a/a.go
Hello, world from b/b.go
-
Package
a
:- The
init
function in packagea
executes first, as it is the first package imported.
- The
-
Package
b
:- The
init
function in packageb
executes next, following the import of packageb
.
- The
-
Main Package:
- The
init
function in themain
package executes after theinit
functions in imported packages.
- The
-
Execution Order:
- The
main
function starts executing last, after allinit
functions have been completed.
- The
The init
function in Go plays a crucial role in package initialization, allowing setup tasks to be performed before the main
function runs. It can be used in multiple ways, including defining multiple init
functions within a file and handling initialization across multiple packages. Understanding the order of execution for init
functions is essential for managing complex initialization logic in Go applications.
In Go, a struct
(short for structure) is a composite data type that groups together variables (fields) under a single name. Structs are used to create complex data types that group different pieces of related data together. They are a foundational concept in Go, enabling the modeling of real-world entities and structures.
A struct is defined using the type
keyword followed by the struct name and the struct
keyword. Fields within a struct are defined with their name and type.
type StructName struct {
FieldName1 FieldType1
FieldName2 FieldType2
// Additional fields
}
type Person struct {
Name string
Age int
Address string
}
Structs can be instantiated and initialized in various ways, including using literal notation or the new
keyword.
You can initialize a struct by providing values for its fields in a struct literal.
// Creating an instance of Person using struct literal
person := Person{
Name: "Alice",
Age: 30,
Address: "123 Wonderland",
}
The new
keyword creates a pointer to a new struct and initializes all fields to their zero values.
// Creating a pointer to a new Person instance
personPtr := new(Person)
personPtr.Name = "Bob"
personPtr.Age = 25
personPtr.Address = "456 Neverland"
Struct fields are accessed using dot notation.
fmt.Println(person.Name) // Output: Alice
fmt.Println(person.Age) // Output: 30
fmt.Println(person.Address) // Output: 123 Wonderland
You can define methods on structs to perform operations on the struct data. Methods are functions with a receiver argument that specifies the struct type.
func (s StructName) MethodName(params) returnType {
// Method body
}
type Person struct {
Name string
Age int
Address string
}
// Method to display person details
func (p Person) DisplayInfo() {
fmt.Printf("Name: %s, Age: %d, Address: %s\n", p.Name, p.Age, p.Address)
}
person := Person{Name: "Charlie", Age: 35, Address: "789 Imaginary"}
person.DisplayInfo() // Output: Name: Charlie, Age: 35, Address: 789 Imaginary
Structs can be used with pointers to avoid copying the entire struct when passing it to functions or methods. This is especially useful for large structs or when you need to modify the original struct.
type Person struct {
Name string
Age int
}
// Method with a pointer receiver to modify the struct
func (p *Person) CelebrateBirthday() {
p.Age++
}
func main() {
person := Person{Name: "David", Age: 40}
person.CelebrateBirthday()
fmt.Println(person.Age) // Output: 41
}
Go supports struct embedding, allowing one struct to include another struct as a field. This provides a way to achieve composition and reuse.
type Address struct {
Street string
City string
}
type Person struct {
Name string
Age int
Address // Embedded struct
}
func main() {
person := Person{
Name: "Eve",
Age: 28,
Address: Address{Street: "101 Main St", City: "Metropolis"},
}
fmt.Println(person.Name) // Output: Eve
fmt.Println(person.Street) // Output: 101 Main St
fmt.Println(person.City) // Output: Metropolis
}
Struct tags are metadata associated with struct fields. They are often used for encoding/decoding with libraries such as JSON or XML.
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
Address string `json:"address"`
}
import (
"encoding/json"
"fmt"
)
func main() {
person := Person{Name: "Frank", Age: 50, Address: "102 Elm St"}
data, _ := json.Marshal(person)
fmt.Println(string(data)) // Output: {"name":"Frank","age":50,"address":"102 Elm St"}
}
Anonymous structs are structs without a named type. They are used for quick and temporary data grouping, often in situations where defining a named struct type is unnecessary.
Anonymous structs are created inline, usually when you need a struct for a specific purpose and don’t need to reuse it elsewhere.
func main() {
// Creating an anonymous struct
person := struct {
Name string
Age int
Address string
}{
Name: "Grace",
Age: 29,
Address: "789 Unknown",
}
fmt.Println(person.Name) // Output: Grace
fmt.Println(person.Age) // Output: 29
fmt.Println(person.Address) // Output: 789 Unknown
}
Anonymous structs are useful for scenarios such as:
- Temporary data storage in functions.
- Returning multiple values from a function in a single structured form.
- Passing complex data between functions or methods without creating a formal type.
Structs in Go are a powerful feature that allows you to define and work with complex data structures. They support various operations such as initialization, field access, method definition, embedding, and the use of struct tags. Anonymous structs provide a way to quickly group data without creating a named type. Understanding structs and their capabilities is essential for effective Go programming.
In Go, anonymous fields (also called embedded fields) are fields in a struct that are declared without a name. These are useful for embedding other types directly into a struct, allowing you to access their methods and fields as if they were part of the struct itself. Here's an overview of how to work with anonymous fields in Go:
### Syntax
To define an anonymous field, you only specify the type, not the field name.
```go
type Person struct {
string // Anonymous field of type string
int // Anonymous field of type int
}
Here is a more practical example using anonymous fields:
package main
import "fmt"
// Define a struct with anonymous fields
type Person struct {
string // Name
int // Age
}
func main() {
// Create an instance of Person
p := Person{"John Doe", 30}
// Access fields directly by their position in the struct
fmt.Println("Name:", p.string) // Accessing the anonymous string field
fmt.Println("Age:", p.int) // Accessing the anonymous int field
}
In this example, the Person
struct has two anonymous fields: a string
for the name and an int
for the age. You can access these fields using the type names, i.e., p.string
for the name and p.int
for the age.
You can also define an anonymous struct without a type name, especially for short-term usage:
package main
import "fmt"
func main() {
// Create an anonymous struct instance
p := struct {
Name string
Age int
}{
Name: "John Doe",
Age: 30,
}
fmt.Println("Name:", p.Name)
fmt.Println("Age:", p.Age)
}
In this case, the struct is not assigned a type name, but you can still use it to create instances and access its fields.
Anonymous fields are often used to embed one struct inside another, which allows you to access fields and methods from the embedded struct directly.
package main
import "fmt"
// Define an Address struct
type Address struct {
Street, City, State string
}
// Define a Person struct embedding Address
type Person struct {
Name string
Age int
Address // Embedded Address struct
}
func main() {
// Create a Person instance with embedded Address
p := Person{
Name: "John Doe",
Age: 30,
Address: Address{
Street: "123 Main St",
City: "Somewhere",
State: "NY",
},
}
// Accessing fields from both Person and Address
fmt.Println("Name:", p.Name)
fmt.Println("Age:", p.Age)
fmt.Println("Street:", p.Address.Street)
fmt.Println("City:", p.Address.City)
fmt.Println("State:", p.Address.State)
}
In this example, the Person
struct embeds the Address
struct. This allows direct access to Address
's fields (Street
, City
, and State
) without needing to reference Address
explicitly.
If you define methods on an anonymous field’s type, you can call those methods directly on the parent struct.
package main
import "fmt"
// Define a struct with methods
type Address struct {
Street, City, State string
}
func (a Address) FullAddress() string {
return a.Street + ", " + a.City + ", " + a.State
}
// Define a Person struct embedding Address
type Person struct {
Name string
Age int
Address // Embedded Address struct
}
func main() {
// Create a Person instance with embedded Address
p := Person{
Name: "John Doe",
Age: 30,
Address: Address{
Street: "123 Main St",
City: "Somewhere",
State: "NY",
},
}
// Call method from the embedded Address struct
fmt.Println("Full Address:", p.FullAddress()) // Direct access to FullAddress method
}
Here, the FullAddress
method of the Address
struct is accessible directly through the Person
struct instance p
.
Anonymous fields in Go are a powerful feature that allows for cleaner and more modular code by embedding types directly into structs. They are especially useful when you want to combine multiple types into a single struct or access methods of embedded types directly.
Here’s the Markdown guide on Go Goroutines and Concurrency Patterns:
# Go Goroutines & Concurrency Patterns - A Comprehensive Guide
Go makes concurrency a first-class citizen with its goroutines and channels, enabling efficient parallel processing. Understanding how to use goroutines and apply concurrency patterns is crucial for writing scalable applications.
This guide will take you through the basics of goroutines, then progress to concurrency patterns in Go, with clear syntax and examples.
## Table of Contents
- [Introduction to Go Goroutines](#introduction-to-go-goroutines)
- [Creating Goroutines](#creating-goroutines)
- [Synchronization in Goroutines](#synchronization-in-goroutines)
- [Waiting for Goroutines to Finish](#waiting-for-goroutines-to-finish)
- [Go Concurrency Patterns](#go-concurrency-patterns)
- [Fan-Out Pattern](#fan-out-pattern)
- [Fan-In Pattern](#fan-in-pattern)
- [Worker Pool Pattern](#worker-pool-pattern)
- [Pipeline Pattern](#pipeline-pattern)
- [Publish-Subscribe Pattern](#publish-subscribe-pattern)
---
## Introduction to Go Goroutines
A **goroutine** is a lightweight thread of execution. It is Go’s way of achieving concurrency without the overhead of traditional threads. Goroutines run concurrently, and the Go runtime schedules them efficiently.
### Syntax to create a Goroutine:
```go
go functionName()
- Goroutines are managed by the Go runtime and are multiplexed onto a smaller number of OS threads.
- Threads are managed by the OS kernel and have much more overhead.
You can create a goroutine by using the go
keyword followed by a function call. When you use go functionName()
, the function will execute concurrently in the background.
package main
import "fmt"
func sayHello() {
fmt.Println("Hello, Goroutine!")
}
func main() {
go sayHello() // Create a goroutine to call sayHello
fmt.Println("Main function is running concurrently")
}
Main function is running concurrently
Hello, Goroutine!
Goroutines run concurrently, but if they share data, synchronization is essential to avoid race conditions. You can use channels, sync
package, or other techniques for synchronization.
If you want to wait for a goroutine to finish before proceeding, you can use sync.WaitGroup
.
package main
import (
"fmt"
"sync"
)
func sayHello(wg *sync.WaitGroup) {
defer wg.Done() // Notify when this goroutine is done
fmt.Println("Hello, Goroutine!")
}
func main() {
var wg sync.WaitGroup
wg.Add(1) // Number of goroutines to wait for
go sayHello(&wg)
wg.Wait() // Wait for all goroutines to finish
fmt.Println("Main function finished")
}
Hello, Goroutine!
Main function finished
Go’s concurrency model provides powerful patterns for structuring concurrent applications. These patterns leverage goroutines and channels for efficient parallel execution.
The Fan-Out Pattern is used when you want multiple goroutines to perform the same task concurrently. Each worker goroutine consumes tasks from the same input channel and processes them.
package main
import "fmt"
func worker(ch chan int, id int) {
for task := range ch {
fmt.Printf("Worker %d processing task %d\n", id, task)
}
}
func main() {
ch := make(chan int)
// Start 3 worker goroutines
for i := 1; i <= 3; i++ {
go worker(ch, i)
}
// Send 5 tasks into the channel
for i := 1; i <= 5; i++ {
ch <- i
}
close(ch) // Close the channel to signal workers to stop
}
Worker 1 processing task 1
Worker 2 processing task 2
Worker 3 processing task 3
Worker 1 processing task 4
Worker 2 processing task 5
The Fan-In Pattern merges multiple channels into one, often used when multiple goroutines generate results that need to be collected into a single channel.
package main
import "fmt"
func generateNumbers(start, end int, ch chan int) {
for i := start; i <= end; i++ {
ch <- i
}
close(ch)
}
func main() {
ch1 := make(chan int)
ch2 := make(chan int)
go generateNumbers(1, 5, ch1)
go generateNumbers(6, 10, ch2)
for num := range merge(ch1, ch2) {
fmt.Println(num)
}
}
func merge(ch1, ch2 chan int) chan int {
ch := make(chan int)
go func() {
for v := range ch1 {
ch <- v
}
for v := range ch2 {
ch <- v
}
close(ch)
}()
return ch
}
1
2
3
4
5
6
7
8
9
10
In the Worker Pool Pattern, you use a pool of workers to process tasks concurrently. This is particularly useful for managing and limiting the number of concurrent goroutines, avoiding overloading the system.
package main
import "fmt"
func worker(id int, ch chan int) {
for task := range ch {
fmt.Printf("Worker %d is processing task %d\n", id, task)
}
}
func main() {
taskCh := make(chan int)
numWorkers := 3
// Create worker goroutines
for i := 1; i <= numWorkers; i++ {
go worker(i, taskCh)
}
// Send tasks to workers
for i := 1; i <= 5; i++ {
taskCh <- i
}
close(taskCh) // Close channel to stop workers
}
Worker 1 is processing task 1
Worker 2 is processing task 2
Worker 3 is processing task 3
Worker 1 is processing task 4
Worker 2 is processing task 5
The Pipeline Pattern is used when you want to break down a task into multiple stages, where each stage is handled by a separate goroutine.
package main
import "fmt"
func stage1(ch chan int) {
for i := 1; i <= 5; i++ {
ch <- i
}
close(ch)
}
func stage2(ch1, ch2 chan int) {
for value := range ch1 {
ch2 <- value * 2
}
close(ch2)
}
func main() {
ch1 := make(chan int)
ch2 := make(chan int)
go stage1(ch1)
go stage2(ch1, ch2)
for result := range ch2 {
fmt.Println("Result:", result)
}
}
Result: 2
Result: 4
Result: 6
Result: 8
Result: 10
In the Publish-Subscribe Pattern, you have multiple subscribers (listeners) that receive data from a single publisher (producer). This pattern is useful when you need to broadcast messages to multiple receivers.
package main
import "fmt"
func publisher(ch chan int) {
for i := 1; i <= 5; i++ {
ch <- i
}
close(ch)
}
func subscriber(id int, ch chan int) {
for msg := range ch {
fmt.Printf("Subscriber %d received: %d\n", id, msg)
}
}
func main() {
ch := make(chan int)
go publisher(ch)
// Create 3 subscribers
for i := 1; i <= 3; i++ {
go subscriber(i, ch)
}
// Wait for the publisher to finish
select {}
}
Subscriber 1 received: 1
Subscriber 2 received: 1
Subscriber 3 received: 1
Subscriber 1 received: 2
Subscriber 2 received: 2
Subscriber 3 received: 2
...
Goroutines are Go's primary tool for concurrency, providing lightweight, easy-to-manage threads. When combined with channels, they allow for efficient communication between concurrent processes.
By using concurrency patterns like Fan-Out, Fan-In, Worker Pools, Pipelines, and Publish-Subscribe, you can design concurrent systems that are scalable, easy to maintain, and performant.
These patterns are just the beginning—Go's simplicity, efficiency, and power allow for much more sophisticated concurrency control, making it one of the best languages for building concurrent applications.
```markdown
# Go Channels
Channels in Go provide a way for goroutines to communicate with each other and synchronize their execution. They allow data to be passed between goroutines safely. Channels are a fundamental part of Go's concurrency model.
This guide will take you through all the essential aspects of Go Channels, from basic concepts to advanced usage.
## Table of Contents
- [Introduction to Go Channels](#introduction-to-go-channels)
- [Creating Channels](#creating-channels)
- [Sending and Receiving Data](#sending-and-receiving-data)
- [Buffered Channels](#buffered-channels)
- [Closing Channels](#closing-channels)
- [Select Statement](#select-statement)
- [Channel Direction](#channel-direction)
- [Range Over Channels](#range-over-channels)
- [Channel of Structs](#channel-of-structs)
- [Channel in Goroutines](#channel-in-goroutines)
- [Advanced Channel Patterns](#advanced-channel-patterns)
## Introduction to Go Channels
A **channel** in Go is a data structure that allows goroutines to communicate with each other. You can send data into a channel from one goroutine and receive it in another.
Channels are typed, meaning that they can only carry values of a specified type. For example, a `chan int` channel can only carry integers.
### Syntax:
```go
var ch chan int
You can create channels using the built-in make()
function. There are two types of channels: unbuffered and buffered.
An unbuffered channel does not have any internal storage. A send operation will block until another goroutine receives the data.
ch := make(chan int)
A buffered channel has a capacity. A send operation will only block when the channel is full.
ch := make(chan int, 3) // Channel with capacity of 3
You can send data into a channel using the <-
operator, and you can receive data from a channel using the same operator.
ch <- value // Send value to channel
value := <-ch // Receive value from channel
package main
import "fmt"
func main() {
ch := make(chan int)
// Goroutine to send data
go func() {
ch <- 42
}()
// Main goroutine receives data
value := <-ch
fmt.Println("Received:", value)
}
Buffered channels allow you to send data without immediate blocking as long as the channel is not full.
package main
import "fmt"
func main() {
ch := make(chan int, 2) // Buffered channel with capacity 2
ch <- 1
ch <- 2
// This will block because the channel is full
// ch <- 3 // Uncommenting this line will cause a deadlock
fmt.Println(<-ch)
fmt.Println(<-ch)
}
Closing a channel indicates that no more values will be sent on it. It is a good practice to close a channel when you are done sending data.
close(ch) // Close the channel
package main
import "fmt"
func main() {
ch := make(chan int)
// Goroutine to send data
go func() {
for i := 1; i <= 3; i++ {
ch <- i
}
close(ch) // Close the channel when done
}()
// Receiving data
for value := range ch {
fmt.Println(value)
}
}
The select
statement allows you to wait on multiple channel operations. It is like a switch
statement but for channels. It lets you handle multiple channels and choose which one to interact with.
select {
case value := <-ch1:
// Do something with value from ch1
case value := <-ch2:
// Do something with value from ch2
case <-time.After(1 * time.Second):
fmt.Println("Timeout")
}
package main
import (
"fmt"
"time"
)
func main() {
ch1 := make(chan string)
ch2 := make(chan string)
go func() {
time.Sleep(2 * time.Second)
ch1 <- "Data from ch1"
}()
go func() {
time.Sleep(1 * time.Second)
ch2 <- "Data from ch2"
}()
select {
case msg := <-ch1:
fmt.Println("Received from ch1:", msg)
case msg := <-ch2:
fmt.Println("Received from ch2:", msg)
}
}
Channels can be restricted to either sending or receiving values by specifying the direction of the channel.
package main
import "fmt"
func sendData(ch chan<- int) {
ch <- 1
}
func receiveData(ch <-chan int) {
value := <-ch
fmt.Println(value)
}
func main() {
ch := make(chan int)
go sendData(ch)
go receiveData(ch)
time.Sleep(1 * time.Second)
}
You can use the range
keyword to iterate over values received from a channel until it is closed.
package main
import "fmt"
func main() {
ch := make(chan int)
go func() {
for i := 1; i <= 3; i++ {
ch <- i
}
close(ch)
}()
for value := range ch {
fmt.Println(value)
}
}
You can send and receive complex data structures (like structs) over channels.
package main
import "fmt"
type Person struct {
Name string
Age int
}
func main() {
ch := make(chan Person)
go func() {
ch <- Person{Name: "John", Age: 30}
}()
person := <-ch
fmt.Println(person.Name, person.Age)
}
You can use channels for synchronizing goroutines and passing data between them. Channels can also help in orchestrating the execution flow in concurrent programs.
package main
import "fmt"
func work(ch chan string) {
fmt.Println("Working...")
ch <- "Done"
}
func main() {
ch := make(chan string)
go work(ch)
msg := <-ch
fmt.Println("Received:", msg)
}
-
Fan-Out Pattern: Multiple goroutines receive from the same channel.
package main import "fmt" func worker(ch chan int) { for v := range ch { fmt.Println("Processing", v) } } func main() { ch := make(chan int) for i := 0; i < 3; i++ { go worker(ch) } for i := 0; i < 10; i++ { ch <- i } close(ch) }
-
Fan-In Pattern: Multiple channels are merged into one channel for processing.
package main import "fmt" func generator(ch chan int) { for i := 0; i < 5; i++ { ch <- i } close(ch) } func main() { ch1 := make(chan int) ch2 := make(chan int) go generator(ch1) go generator(ch2) for i := range merge(ch1, ch2) { fmt.Println(i) } } func merge(ch1, ch2 chan int) chan int { ch := make(chan int) go func() { for v := range ch1 { ch <- v } for v := range ch2 { ch <- v } close(ch) }() return ch }
Channels are a core concept in Go for handling concurrency. They enable safe communication between goroutines, making it easier to build concurrent applications.
By understanding how to create, use, and synchronize channels, you can effectively manage goroutines and implement complex concurrency patterns.
---
# Best Practices and Go Idioms
Go is designed to be a simple, fast, and readable language. Writing **idiomatic Go** means writing code that feels natural within the Go ecosystem, leveraging the language's strengths while adhering to conventions. Here are some **best practices** and **Go idioms** that every Go developer should be familiar with.
## Table of Contents
- [Go Best Practices](#go-best-practices)
- [Code Formatting](#code-formatting)
- [Naming Conventions](#naming-conventions)
- [Error Handling](#error-handling)
- [Avoiding Global State](#avoiding-global-state)
- [Documentation and Comments](#documentation-and-comments)
- [Concurrency Best Practices](#concurrency-best-practices)
- [Testing Best Practices](#testing-best-practices)
- [Go Idioms](#go-idioms)
- [Idiomatic Error Handling](#idiomatic-error-handling)
- [Using Multiple Return Values](#using-multiple-return-values)
- [Defer, Panic, and Recover](#defer-panic-and-recover)
- [Slices, Maps, and Arrays](#slices-maps-and-arrays)
- [Channel Communication](#channel-communication)
- [The Blank Identifier](#the-blank-identifier)
---
## Go Best Practices
### Code Formatting
Go has an official code formatting tool, `gofmt`, that automatically formats your Go code to the standard style. **Always use `gofmt`** (or an IDE plugin) to format your code, ensuring that it is consistently readable and follows Go conventions.
#### Example:
```bash
gofmt -w .
This command formats the code in the current directory and writes the changes back to the files.
In Go, naming is crucial for clarity and readability. Here are some key guidelines:
- Use short, concise names for variables: Go favors short names for local variables, especially in functions. For example,
i
,j
, andk
for loop indices. - Use camelCase for variable names and function names:
var userName string func calculateArea() int
- Use PascalCase for exported names: If a function, type, or variable needs to be accessible outside the package, its name should begin with an uppercase letter:
func ServeHTTP(w http.ResponseWriter, r *http.Request) { ... }
- Avoid unnecessary abbreviations: Keep names descriptive but not overly verbose.
Error handling is one of Go’s distinguishing features. Unlike exceptions, Go uses explicit error returns. Always handle errors, even if it's just logging them.
package main
import (
"fmt"
"os"
)
func readFile(filename string) ([]byte, error) {
file, err := os.Open(filename)
if err != nil {
return nil, fmt.Errorf("could not open file: %w", err)
}
defer file.Close()
content, err := os.ReadFile(filename)
if err != nil {
return nil, fmt.Errorf("could not read file: %w", err)
}
return content, nil
}
func main() {
content, err := readFile("example.txt")
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Println("File content:", string(content))
}
Go encourages modularity and testability. Avoid global state, especially in large projects, as it makes code harder to test and reason about.
Instead of using global variables, pass data explicitly via function arguments or struct fields.
- Write clear, concise comments explaining why the code exists, not just what it does.
- Use doc comments (i.e., comments above functions, structs, and variables) to document exported entities.
- Go uses GoDoc to automatically generate documentation from comments.
// CalculateArea returns the area of a rectangle given its width and height.
func CalculateArea(width, height float64) float64 {
return width * height
}
-
Avoid shared state between goroutines as much as possible. If you must share data, use channels or synchronization primitives (e.g.,
sync.Mutex
). -
Use
sync.WaitGroup
to wait for multiple goroutines to finish.var wg sync.WaitGroup for i := 0; i < 5; i++ { wg.Add(1) go func(i int) { defer wg.Done() fmt.Println(i) }(i) } wg.Wait()
- Write tests: Go encourages testing, and the standard library comes with the
testing
package. - Use
t.Helper()
to mark helper functions in tests. - Keep tests simple: Write unit tests that test small pieces of functionality.
func TestCalculateArea(t *testing.T) {
result := CalculateArea(5, 10)
expected := 50.0
if result != expected {
t.Errorf("expected %v, got %v", expected, result)
}
}
The idiomatic Go error handling pattern is to return an error as the last return value and check it immediately. This reduces the need for try-catch blocks and helps write clear and explicit error handling.
if err != nil {
return nil, err
}
Go’s multiple return values are commonly used to return both the result and an error. This pattern is idiomatic and a core part of Go’s error handling approach.
func divide(a, b int) (int, error) {
if b == 0 {
return 0, fmt.Errorf("cannot divide by zero")
}
return a / b, nil
}
-
defer
: Used to schedule a function to run after the surrounding function completes. Useful for cleanup tasks.func fileOperation() { file, err := os.Open("file.txt") if err != nil { log.Fatal(err) } defer file.Close() // Work with the file }
-
panic
: Used to stop the normal execution of a program. Should be reserved for unrecoverable errors.if err != nil { panic("Something went wrong!") }
-
recover
: Used inside a deferred function to catch panics and prevent program crashes.
-
Slices: Use slices, not arrays, for most collection-based tasks. They are more flexible and powerful.
names := []string{"Alice", "Bob", "Charlie"} names = append(names, "David")
-
Maps: For key-value pairs, use maps. Maps provide constant time lookups.
capitals := map[string]string{ "France": "Paris", "Italy": "Rome", } capitals["Spain"] = "Madrid"
-
Arrays: Use arrays only when the size is fixed and known ahead of time. Otherwise, use slices.
-
Channel direction: Channels can be send-only or receive-only. This helps in restricting the operations that can be done on channels, improving code clarity.
func sendData(ch chan<- int) { ch <- 42 } func receiveData(ch <-chan int) int { return <-ch }
-
Buffered channels: When you want to send multiple messages to a channel without blocking, use a buffered channel.
The blank identifier (_
) is used when you want to ignore a value or return from a function but don’t need the value.
name, _ := getPersonInfo() // Ignore the error returned by getPersonInfo
By following these best practices and idioms, you can write clean, idiomatic Go code that is easy to maintain, efficient, and scalable. The key to mastering Go is understanding its core principles like simplicity, clarity, and effective error handling.