Lesson 5: Functions¶
Big Picture¶
flowchart LR
IN["Parameters<br/>(a, b int)"] --> F["func name"]
F --> OUT["Return values"]
OUT --> O1["single: int"]
OUT --> O2["multiple: (int, error)"]
OUT --> O3["named: (result int)"]
F --> V["variadic: nums ...int → slice"]
F --> FN["functions are values →<br/>assign, pass, return"]
FN --> CL["closure: captures outer variables"]
Functions are first-class citizens — you can store them in variables, pass them as arguments, and return them. Closures carry their captured environment with them.
Concepts¶
Basic Function¶
Methods and Receivers¶
This syntax defines a method with a pointer receiver:
Read the declaration from left to right:
funcdeclares a function or method.(m *Manager)is the receiver. It attaches the method toManager, andmis the name used to access that value inside the method.*Manageris a pointer receiver, so the method can modify the originalManager.AddEmployeeis the method name. Its capital letter makes it accessible from other packages whenManageris also exported.(e Employee)accepts one parameter namedeof typeEmployee.- There is no return type, so the method returns no value.
m.Employeesaccesses the receiver'sEmployeesfield.append(m.Employees, e)returns a slice containing the new employee, which must be assigned back tom.Employees.
A complete example looks like this:
package main
import "fmt"
type Employee struct {
Name string
}
type Manager struct {
Employees []Employee
}
func (m *Manager) AddEmployee(e Employee) {
m.Employees = append(m.Employees, e)
}
func main() {
manager := Manager{}
manager.AddEmployee(Employee{Name: "Ava"})
fmt.Println(manager.Employees[0].Name)
}
Use a pointer receiver when the method needs to modify the receiver or when copying the value would be expensive. A value receiver, such as func (m Manager) EmployeeCount() int, receives a copy and is suitable for small values that the method only reads.
Go automatically takes the address in manager.AddEmployee(...) when manager is addressable, so you usually do not need to write (&manager).AddEmployee(...).
Go also does not require semicolons at the end of normal statements. Prefer append(m.Employees, e) rather than append(m.Employees,e); because gofmt follows the spaced, semicolon-free style.
Return Values¶
func add(a, b int) int {
return a + b
}
// Multiple returns
func divide(a, b int) (int, error) {
return a / b, nil
}
// Named returns
func calc() (result int) {
result = 42
return
}
Variadic Functions¶
Closures¶
Code Walkthrough¶
package main
import "fmt"
func divide(a, b int) (int, error) {
if b == 0 {
return 0, fmt.Errorf("divide by zero")
}
return a / b, nil
}
func sum(nums ...int) (total int) { // named return
for _, n := range nums { total += n }
return
}
func main() {
fmt.Println(divide(10, 2))
fmt.Println(sum(1, 2, 3))
}
Full program: code/05_functions.go.
Exercises
- Write a variadic function
max(nums ...int) intthat returns the largest value. - Return a function from a function — e.g.,
func multiplier(factor int) func(int) int. - Rewrite a named-return function to use explicit returns. Which is clearer?
Check Your Understanding¶
What does func divide(a, b int) (int, error) let Go do that Java can't (directly)?
Inside func sum(nums ...int), what type is nums?