Easy📖Теория7 min

Функции

Объявление функций, множественные возвращаемые значения, замыкания, методы и init()

Функции

Объявление функций

В 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)
}

Проверь себя

🧪

Что лучше использовать — value или pointer receiver — если метод изменяет поля структуры?

🧪

Замыкания в Go захватывают переменные из окружающей области видимости по...

🧪

Какой результат выполнения этого кода? ```go func calc() (result int) { defer func() { result += 10 }() return 5 } fmt.Println(calc()) ```

🧪

Когда вызывается функция `init()`?

🧪

Что произойдёт при вызове `sum()` без аргументов, если функция определена как `func sum(numbers ...int) int`?