Functions in Go
Functions are first-class citizens in Go. They can be assigned to variables, passed as arguments, and returned from other functions.
Basic Function Syntax
func functionName(param1 type1, param2 type2) returnType {
// function body
return value
}
Simple Examples
// No parameters, no return
func sayHello() {
fmt.Println("Hello!")
}
// With parameters
func greet(name string) {
fmt.Printf("Hello, %s!\n", name)
}
// With return value
func add(a int, b int) int {
return a + b
}
// Shortened parameter declaration (same type)
func multiply(a, b int) int {
return a * b
}
Multiple Return Values
Go functions can return multiple values. This is idiomatic and heavily used:
func divide(a, b float64) (float64, error) {
if b == 0 {
return 0, errors.New("division by zero")
}
return a / b, nil
}
// Calling
result, err := divide(10, 2)
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Println("Result:", result)
Named Return Values
You can name return values, which acts as documentation and allows "naked returns":
func rectangle(width, height float64) (area, perimeter float64) {
area = width * height
perimeter = 2 * (width + height)
return // Naked return (returns named values)
}
// Call
a, p := rectangle(5, 3)
Note: Naked returns can reduce readability in long functions. Use them sparingly.
Ignoring Return Values
Use _ to ignore values you don't need:
result, _ := divide(10, 2) // Ignore error (not recommended for errors!)
// Common with maps
value, ok := myMap[key]
if !ok {
// key not found
}
_, exists := myMap[key] // Just check existence
Variadic Functions
Functions that accept a variable number of arguments:
func sum(nums ...int) int {
total := 0
for _, n := range nums {
total += n
}
return total
}
// Calling
sum(1, 2, 3) // 6
sum(1, 2, 3, 4, 5) // 15
sum() // 0
// Passing a slice (spread operator)
numbers := []int{1, 2, 3, 4}
sum(numbers...) // 10
Variadic with Other Parameters
Variadic parameter must be last:
func printf(format string, args ...interface{}) {
// format is required, args is variadic
}
Anonymous Functions
Functions without a name, often used inline:
// Assigned to a variable
add := func(a, b int) int {
return a + b
}
result := add(3, 4) // 7
// Immediately invoked
result := func(a, b int) int {
return a + b
}(3, 4) // 7
// Common with goroutines
go func() {
fmt.Println("Running in a goroutine")
}()
Closures
A closure is a function that references variables from outside its body:
func counter() func() int {
count := 0
return func() int {
count++
return count
}
}
c := counter()
fmt.Println(c()) // 1
fmt.Println(c()) // 2
fmt.Println(c()) // 3
c2 := counter()
fmt.Println(c2()) // 1 (separate counter)
Closure Gotcha with Loops
// WRONG - all goroutines share the same 'i'
for i := 0; i < 3; i++ {
go func() {
fmt.Println(i) // Probably prints 3, 3, 3
}()
}
// CORRECT - pass 'i' as a parameter
for i := 0; i < 3; i++ {
go func(n int) {
fmt.Println(n) // Prints 0, 1, 2 (order may vary)
}(i)
}
// Also correct in Go 1.22+ - loop variable is now per-iteration
for i := 0; i < 3; i++ {
go func() {
fmt.Println(i) // Works correctly in Go 1.22+
}()
}
Functions as Values
Functions are first-class values:
// Function type
type MathFunc func(int, int) int
func apply(fn MathFunc, a, b int) int {
return fn(a, b)
}
add := func(a, b int) int { return a + b }
mul := func(a, b int) int { return a * b }
fmt.Println(apply(add, 3, 4)) // 7
fmt.Println(apply(mul, 3, 4)) // 12
Function as Parameter
// Higher-order function
func filter(nums []int, test func(int) bool) []int {
var result []int
for _, n := range nums {
if test(n) {
result = append(result, n)
}
}
return result
}
// Usage
numbers := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
evens := filter(numbers, func(n int) bool {
return n%2 == 0
})
// evens: [2, 4, 6, 8, 10]
greaterThan5 := filter(numbers, func(n int) bool {
return n > 5
})
// greaterThan5: [6, 7, 8, 9, 10]
Function as Return Value
func makeMultiplier(factor int) func(int) int {
return func(n int) int {
return n * factor
}
}
double := makeMultiplier(2)
triple := makeMultiplier(3)
fmt.Println(double(5)) // 10
fmt.Println(triple(5)) // 15
Methods
Methods are functions with a receiver. Covered in detail in structs, but here's a preview:
type Rectangle struct {
Width, Height float64
}
// Method with value receiver
func (r Rectangle) Area() float64 {
return r.Width * r.Height
}
// Method with pointer receiver
func (r *Rectangle) Scale(factor float64) {
r.Width *= factor
r.Height *= factor
}
rect := Rectangle{Width: 10, Height: 5}
fmt.Println(rect.Area()) // 50
rect.Scale(2)
fmt.Println(rect.Area()) // 200
Init Functions
Special functions that run before main():
package main
import "fmt"
var message string
func init() {
// Runs before main
message = "Hello from init!"
}
func main() {
fmt.Println(message)
}
- Each file can have multiple
initfunctions - They run in the order they appear
- Package init functions run after their imports' init functions
- Use for initialization that can't be done with simple assignment
// Common use: register database drivers
import _ "github.com/lib/pq" // Just runs init() to register driver
Recursion
Go supports recursive functions:
func factorial(n int) int {
if n <= 1 {
return 1
}
return n * factorial(n-1)
}
func fibonacci(n int) int {
if n <= 1 {
return n
}
return fibonacci(n-1) + fibonacci(n-2)
}
Note: Go doesn't guarantee tail call optimization, so deep recursion can cause stack overflow. For performance-critical code, prefer iteration.
Best Practices
Keep Functions Focused
// Good: single responsibility
func validateEmail(email string) bool { ... }
func sendEmail(to, subject, body string) error { ... }
// Bad: doing too much
func validateAndSendEmail(to, subject, body string) error { ... }
Accept Interfaces, Return Structs
// Good: accepts interface (flexible)
func process(r io.Reader) error { ... }
// Good: returns concrete type
func newBuffer() *bytes.Buffer { ... }
Use Meaningful Parameter Names
// Good
func transfer(from, to Account, amount Money) error
// Less clear
func transfer(a, b Account, m Money) error
Error Returns Last
// Idiomatic
func Read(filename string) ([]byte, error)
// Not idiomatic
func Read(filename string) (error, []byte)
Avoid Global State
// Bad: uses global state
var db *sql.DB
func GetUser(id int) (*User, error) {
return db.Query(...)
}
// Good: explicit dependency
func GetUser(db *sql.DB, id int) (*User, error) {
return db.Query(...)
}
// Or use a struct with methods
type UserStore struct {
db *sql.DB
}
func (s *UserStore) GetUser(id int) (*User, error) {
return s.db.Query(...)
}
Next Steps
Continue to 05-collections.md to learn about arrays, slices, and maps.