Функции
Объявление функций
В Go функции — полноценные объекты первого класса (first-class citizens). Их можно присваивать переменным, передавать как аргументы и возвращать из других функций.
Базовый синтаксис
package main
import "fmt"
// Simple function with no parameters and no return value
func greet() {
fmt.Println("Hello, World!")
}
// Function with parameters
func add(a int, b int) int {
return a + b
}
// Shortened parameter syntax (same type)
func multiply(a, b int) int {
return a * b
}
// Multiple return values
func divide(a, b float64) (float64, error) {
if b == 0 {
return 0, fmt.Errorf("division by zero")
}
return a / b, nil
}
func main() {
greet()
sum := add(3, 5)
fmt.Println("Sum:", sum) // 8
result, err := divide(10, 3)
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Printf("Result: %.2f\n", result) // 3.33
}
Множественные возвращаемые значения
Множественный возврат — одна из ключевых особенностей Go, особенно для обработки ошибок.
package main
import (
"errors"
"fmt"
"os"
)
// Return value and error — THE Go pattern
func readConfig(path string) ([]byte, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("read config %s: %w", path, err)
}
return data, nil
}
// Return multiple values of different types
func minMax(numbers []int) (int, int, error) {
if len(numbers) == 0 {
return 0, 0, errors.New("empty slice")
}
min, max := numbers[0], numbers[0]
for _, n := range numbers[1:] {
if n < min {
min = n
}
if n > max {
max = n
}
}
return min, max, nil
}
func main() {
min, max, err := minMax([]int{3, 1, 4, 1, 5, 9, 2, 6})
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Printf("Min: %d, Max: %d\n", min, max) // Min: 1, Max: 9
// Ignore a return value with blank identifier
_, maxOnly, _ := minMax([]int{10, 20, 30})
fmt.Println("Max:", maxOnly) // 30
}
Именованные возвращаемые значения
Именованные возвращаемые значения объявляются в сигнатуре функции и инициализируются нулевыми значениями. Можно использовать «голый» return (naked return).
package main
import (
"fmt"
"math"
)
// Named return values
func rectangleProps(length, width float64) (area, perimeter float64) {
area = length * width
perimeter = 2 * (length + width)
return // Naked return — returns area and perimeter
}
// Named returns are useful for documentation
func parseCoordinates(input string) (lat, lon float64, err error) {
// Named return values document what the function returns
_, err = fmt.Sscanf(input, "%f,%f", &lat, &lon)
if err != nil {
return 0, 0, fmt.Errorf("parse coordinates %q: %w", input, err)
}
if math.Abs(lat) > 90 || math.Abs(lon) > 180 {
return 0, 0, fmt.Errorf("invalid coordinates: lat=%f, lon=%f", lat, lon)
}
return // lat, lon, nil
}
func main() {
area, perimeter := rectangleProps(5, 3)
fmt.Printf("Area: %.1f, Perimeter: %.1f\n", area, perimeter)
lat, lon, err := parseCoordinates("55.7558,37.6173")
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Printf("Moscow: lat=%.4f, lon=%.4f\n", lat, lon)
}
Совет: Naked return следует использовать только в коротких функциях. В длинных функциях явный return улучшает читаемость.
Вариативные функции (Variadic)
Вариативная функция принимает произвольное количество аргументов одного типа.
package main
import "fmt"
// Variadic parameter must be the last one
func sum(numbers ...int) int {
total := 0
for _, n := range numbers {
total += n
}
return total
}
// Mix regular and variadic parameters
func printf(format string, args ...any) {
fmt.Printf(format+"\n", args...)
}
// Spread operator — pass slice as variadic arguments
func main() {
// Call with individual arguments
fmt.Println(sum(1, 2, 3)) // 6
fmt.Println(sum(1, 2, 3, 4, 5)) // 15
fmt.Println(sum()) // 0
// Spread a slice into variadic arguments
numbers := []int{10, 20, 30}
fmt.Println(sum(numbers...)) // 60
// fmt.Println itself is variadic
fmt.Println("Hello", "World", 42) // Hello World 42
printf("Name: %s, Age: %d", "Go", 15)
}
| Аспект | Описание |
|---|---|
| Синтаксис | func name(args ...T) |
| Позиция | Только последний параметр |
| Внутри функции | args имеет тип []T |
| Вызов со слайсом | name(slice...) |
| Без аргументов | args будет nil (не пустой слайс) |
Функции как значения первого класса
package main
import (
"fmt"
"math"
"strings"
)
// Function type
type MathFunc func(float64) float64
// Function as parameter
func apply(f MathFunc, value float64) float64 {
return f(value)
}
// Function returning a function
func multiplier(factor float64) MathFunc {
return func(x float64) float64 {
return x * factor
}
}
// Higher-order function: map
func mapInts(slice []int, fn func(int) int) []int {
result := make([]int, len(slice))
for i, v := range slice {
result[i] = fn(v)
}
return result
}
// Higher-order function: filter
func filterStrings(slice []string, predicate func(string) bool) []string {
var result []string
for _, s := range slice {
if predicate(s) {
result = append(result, s)
}
}
return result
}
func main() {
// Assign function to variable
var sqrt MathFunc = math.Sqrt
fmt.Println(sqrt(16)) // 4
// Pass function as argument
fmt.Println(apply(math.Abs, -42)) // 42
fmt.Println(apply(math.Sqrt, 144)) // 12
// Function returning function
double := multiplier(2)
triple := multiplier(3)
fmt.Println(double(5)) // 10
fmt.Println(triple(5)) // 15
// Higher-order functions
nums := []int{1, 2, 3, 4, 5}
squared := mapInts(nums, func(n int) int { return n * n })
fmt.Println(squared) // [1 4 9 16 25]
words := []string{"Go", "is", "awesome", "and", "fast"}
long := filterStrings(words, func(s string) bool {
return len(s) > 2
})
fmt.Println(long) // [awesome and fast]
_ = strings.Map // Standard library also uses functions as values
}
Замыкания (Closures)
Замыкание — функция, которая захватывает переменные из окружающего scope. В Go замыкания захватывают переменные по ссылке.
package main
import "fmt"
// Counter using closure
func newCounter() func() int {
count := 0 // Captured by reference
return func() int {
count++
return count
}
}
// Accumulator
func newAccumulator(initial float64) func(float64) float64 {
sum := initial
return func(value float64) float64 {
sum += value
return sum
}
}
// Fibonacci generator
func fibonacci() func() int {
a, b := 0, 1
return func() int {
result := a
a, b = b, a+b
return result
}
}
// Middleware pattern using closures
type Handler func(string) string
func withLogging(h Handler) Handler {
return func(input string) string {
fmt.Printf("Calling with input: %s\n", input)
result := h(input)
fmt.Printf("Result: %s\n", result)
return result
}
}
func main() {
// Each call to newCounter creates independent state
counter1 := newCounter()
counter2 := newCounter()
fmt.Println(counter1()) // 1
fmt.Println(counter1()) // 2
fmt.Println(counter2()) // 1 (independent)
fmt.Println(counter1()) // 3
// Accumulator
acc := newAccumulator(100)
fmt.Println(acc(10)) // 110
fmt.Println(acc(20)) // 130
fmt.Println(acc(-50)) // 80
// Fibonacci
fib := fibonacci()
for range 10 {
fmt.Print(fib(), " ") // 0 1 1 2 3 5 8 13 21 34
}
fmt.Println()
// Middleware
handler := func(name string) string {
return "Hello, " + name
}
logged := withLogging(handler)
logged("Go")
}
Внимание: Замыкания в Go захватывают переменные по ссылке, а не по значению. Изменение переменной внутри замыкания влияет на оригинал и наоборот.
Анонимные функции
package main
import (
"fmt"
"sort"
)
func main() {
// Immediately invoked function expression (IIFE)
result := func(a, b int) int {
return a + b
}(3, 4)
fmt.Println(result) // 7
// Anonymous function as goroutine
done := make(chan bool)
go func() {
fmt.Println("Running in goroutine")
done <- true
}()
<-done
// Anonymous function for sorting
people := []struct {
Name string
Age int
}{
{"Alice", 30},
{"Bob", 25},
{"Charlie", 35},
}
sort.Slice(people, func(i, j int) bool {
return people[i].Age < people[j].Age
})
fmt.Println(people) // [{Bob 25} {Alice 30} {Charlie 35}]
}
Функция init()
init() — специальная функция, которая вызывается автоматически до main(). Каждый файл может содержать несколько init().
package main
import "fmt"
var config map[string]string
// init is called before main
func init() {
fmt.Println("init 1: initializing config")
config = make(map[string]string)
config["env"] = "development"
}
// Multiple init functions in the same file are allowed
func init() {
fmt.Println("init 2: setting defaults")
config["port"] = "8080"
}
func main() {
fmt.Println("main:", config)
}
// Output:
// init 1: initializing config
// init 2: setting defaults
// main: map[env:development port:8080]
Порядок инициализации
1. Импортированные пакеты (рекурсивно, в порядке зависимостей)
2. Переменные уровня пакета (в порядке объявления)
3. Функции init() (в порядке появления в файлах)
4. main()
Совет: Минимизируйте использование
init(). Предпочитайте явную инициализацию через конструкторы.init()усложняет тестирование и создаёт скрытые зависимости.
Методы (Value vs Pointer Receivers)
Метод — это функция с получателем (receiver). В Go методы можно определять на любом именованном типе (кроме интерфейсов и указателей).
package main
import (
"fmt"
"math"
)
type Circle struct {
Radius float64
}
// Value receiver — gets a COPY of the struct
// Use when: method doesn't modify the struct, struct is small
func (c Circle) Area() float64 {
return math.Pi * c.Radius * c.Radius
}
func (c Circle) Perimeter() float64 {
return 2 * math.Pi * c.Radius
}
// Pointer receiver — gets a pointer to the struct
// Use when: method modifies the struct, struct is large, consistency
func (c *Circle) Scale(factor float64) {
c.Radius *= factor // Modifies the original
}
func (c Circle) String() string {
return fmt.Sprintf("Circle(r=%.2f)", c.Radius)
}
func main() {
c := Circle{Radius: 5}
fmt.Printf("Area: %.2f\n", c.Area()) // 78.54
fmt.Printf("Perimeter: %.2f\n", c.Perimeter()) // 31.42
c.Scale(2) // Radius becomes 10
fmt.Printf("After scaling: %s\n", c) // Circle(r=10.00)
// Go automatically takes address/dereferences as needed
p := &Circle{Radius: 3}
fmt.Println(p.Area()) // Go dereferences: (*p).Area()
v := Circle{Radius: 3}
v.Scale(2) // Go takes address: (&v).Scale(2)
}
Когда использовать pointer vs value receiver
| Критерий | Value receiver | Pointer receiver |
|---|---|---|
| Модификация | Не изменяет структуру | Изменяет структуру |
| Размер структуры | Маленькая (< ~64 байт) | Большая |
| Консистентность | — | Если хотя бы один метод — pointer, все должны быть pointer |
| nil receiver | Паника при вызове | Можно обработать nil |
| Интерфейсы | Удовлетворяет и *T, и T | Удовлетворяет только *T |
Методы на пользовательских типах
package main
import (
"fmt"
"strings"
)
// Methods on non-struct types
type StringSlice []string
func (ss StringSlice) Join(sep string) string {
return strings.Join(ss, sep)
}
func (ss StringSlice) Filter(fn func(string) bool) StringSlice {
var result StringSlice
for _, s := range ss {
if fn(s) {
result = append(result, s)
}
}
return result
}
type Celsius float64
func (c Celsius) String() string {
return fmt.Sprintf("%.1f°C", c)
}
func main() {
words := StringSlice{"hello", "world", "go", "programming"}
long := words.Filter(func(s string) bool { return len(s) > 3 })
fmt.Println(long.Join(", ")) // hello, world, programming
temp := Celsius(36.6)
fmt.Println(temp) // 36.6°C (String() is called automatically by fmt)
}