---
title: "Функции"
description: "Представьте, что сдачу из главы о переменных нужно посчитать в трёх местах программы. Можно трижды скопировать формулу, но тогда исправлять ошибку в ней тоже придётся трижды…"
canonical: https://graphlms.ru/go-basics/book/functions
course: "Основы Go"
updated: 2026-10-12
lang: ru
publisher: GraphLMS
---

# Функции

> Источник: https://graphlms.ru/go-basics/book/functions — курс «Основы Go», GraphLMS.

## О чём глава

Представьте, что сдачу из главы о переменных нужно посчитать в трёх местах
программы. Можно трижды скопировать формулу, но тогда исправлять ошибку в ней
тоже придётся трижды. Функция решает это: кусок логики получает имя, принимает
входные данные и возвращает результат. Вы уже вызывали чужие функции —
`fmt.Println`, `strconv.Atoi`. Теперь будем писать свои.

У функций в Go есть черты, которых нет в Python или Java: функция может вернуть
**несколько значений** (отсюда идиома `value, err`), принимать переменное число
аргументов и сама является **значением** — её можно положить в переменную,
передать аргументом и вернуть из другой функции. Глава идёт от простого
объявления к замыканиям, которые «помнят» своё состояние; замыкания понадобятся
в последней главе, при запуске горутин.

## Объявление, параметры и возврат

Базовая форма такая:

```go
func add(a int, b int) int {
    return a + b
}
```

Читается слева направо: ключевое слово `func`, имя, список параметров в
скобках, затем тип результата, затем тело. Тип параметра пишется **после**
имени — это общее правило Go (`a int`, а не `int a`).

Если у нескольких параметров подряд один тип, его можно указать один раз —
в самом конце группы:

```go
func add(a, b int) int { // a и b оба int
    return a + b
}
```

Функция без результата просто не указывает тип после скобок:

```go
func greet(name string) {
    fmt.Println("Привет,", name)
}
```

Аргументы в Go всегда передаются **по значению** — функция получает копию. Если
передать число или строку и изменить параметр внутри, снаружи ничего не
изменится. (Для срезов, карт и указателей копируется сам «дескриптор» или адрес,
поэтому через них можно менять данные — но это тема других глав.)

```go play
package main

import "fmt"

func double(x int) int {
	return x * 2
}

func main() {
	n := 21
	fmt.Println("вход:", n)
	fmt.Println("результат:", double(n))
	fmt.Println("n не изменился:", n)
}
```

Вывод:

```
вход: 21
результат: 42
n не изменился: 21
```

## Множественный возврат и идиома value, err

Функция может вернуть **сразу несколько значений**. Типы
результатов перечисляются в скобках после параметров.

```go
func divmod(a, b int) (int, int) {
    return a / b, a % b
}
```

Это не кортеж и не специальная структура — просто несколько значений, которые
вызывающий код разбирает по переменным:

```go
q, r := divmod(17, 5) // q == 3, r == 2
```

Самое частое применение — пара «результат и ошибка». В Go нет исключений: вместо
того чтобы «бросать» ошибку, функция возвращает её последним значением. Если всё
хорошо — ошибка `nil`. Это и есть идиома `value, err`, которую вы будете видеть
буквально везде:

```go play
package main

import (
	"errors"
	"fmt"
)

func safeDiv(a, b int) (int, error) {
	if b == 0 {
		return 0, errors.New("деление на ноль")
	}
	return a / b, nil
}

func main() {
	if res, err := safeDiv(10, 2); err == nil {
		fmt.Println("10 / 2 =", res)
	}

	if _, err := safeDiv(10, 0); err != nil {
		fmt.Println("ошибка:", err)
	}
}
```

Вывод:

```
10 / 2 = 5
ошибка: деление на ноль
```

Обратите внимание на два приёма. Первый — `if res, err := ...; err == nil`:
объявляем переменные прямо в `if` и тут же проверяем ошибку, область видимости
ограничена телом `if`. Второй — `_` (пустой идентификатор): так мы явно
говорим «это значение мне не нужно». Игнорировать возвращённую ошибку молча —
плохая привычка; либо обрабатывайте её, либо осознанно отбрасывайте через `_`.

## Короткое отступление: срезы

В следующих примерах встречается `[]int{2, 4}` — список целых чисел, который в Go называют **срезом**. Квадратные скобки без числа задают тип, в фигурных перечислены значения. Индексы начинаются с нуля: `nums[0]` — первый элемент. `for i, v := range nums` перебирает элементы: `i` получает индекс, `v` — значение. Если индекс не нужен, вместо имени пишут `_`. Подробно срезы разобраны в главе [«Срезы, карты, строки»](/go-basics/book/collections); здесь хватит этих правил.

## Именованные возвраты

Результатам можно дать имена прямо в сигнатуре. Тогда они работают как
заранее объявленные переменные, а `return` без аргументов («голый return»)
вернёт их текущие значения.

```go
func parseRange(total, parts int) (size, remainder int) {
    size = total / parts
    remainder = total % parts
    return // вернёт size и remainder
}
```

Иногда это удобно: имена документируют, что именно возвращается, и хорошо
сочетаются с `defer`, которому нужно подправить результат перед выходом.

Но злоупотреблять не стоит. В длинной функции «голый return» заставляет
читателя прокручивать код вверх, чтобы понять, что же вернулось. Поэтому
практическое правило простое: **именованные возвраты — для коротких функций**,
где всё видно с одного экрана. В длинных пишите значения в `return` явно.

```go play
package main

import "fmt"

// minMax возвращает минимум и максимум из трёх чисел.
func minMax(a, b, c int) (lo, hi int) {
	lo, hi = a, a
	for _, v := range []int{b, c} {
		if v < lo {
			lo = v
		}
		if v > hi {
			hi = v
		}
	}
	return // голый return: вернёт lo и hi
}

func main() {
	lo, hi := minMax(7, 2, 9)
	fmt.Println("min =", lo, "max =", hi)
}
```

Вывод:

```
min = 2 max = 9
```

## Variadic: переменное число аргументов

Иногда заранее неизвестно, сколько аргументов придёт. Тогда последний параметр
объявляют с многоточием — `...T`. Внутри функции он виден как обычный срез
`[]T`, а вызвать можно с любым числом аргументов, включая ноль.

```go play
package main

import "fmt"

func sum(nums ...int) int {
	total := 0
	for _, n := range nums {
		total += n
	}
	return total
}

func main() {
	fmt.Println(sum(1, 2, 3))    // 6
	fmt.Println(sum(10, 20))     // 30
	fmt.Println(sum())           // 0 — ноль аргументов это нормально
}
```

Вывод:

```
6
30
0
```

А если у вас уже есть готовый срез и его нужно «разложить» в variadic-функцию?
Для этого при вызове ставят `...` после среза — это раскрывает его в отдельные
аргументы:

```go
nums := []int{4, 5, 6}
fmt.Println(sum(nums...)) // 15, эквивалент sum(4, 5, 6)
```

Знакомый `fmt.Println` — тоже variadic-функция. Её параметр объявлен как
`...any`, где `any` означает «значение любого типа» (подробнее в главе об
[интерфейсах](/go-basics/book/interfaces)). Поэтому ей можно передать сколько
угодно значений разных типов.

## Функции как значения

В Go функция — это **значение первого класса**. Её можно присвоить переменной,
у которой будет тип-функция, и потом вызвать через эту переменную:

```go play
package main

import "fmt"

func main() {
	var op func(int, int) int // переменная типа «функция (int,int) int»

	op = func(a, b int) int { return a + b }
	fmt.Println("сумма:", op(3, 4))

	op = func(a, b int) int { return a * b }
	fmt.Println("произведение:", op(3, 4))
}
```

Вывод:

```
сумма: 7
произведение: 12
```

Справа от `op =` стоит функция без имени — *анонимная функция*. Она
записывается как обычная, только имя после `func` пропущено. Именно такую
функцию откладывают в `defer func() { ... }()`, который мелькнул в прошлой
главе: скобки `()` в конце сразу её вызывают.

Раз функция — это значение, её можно **передавать в другую функцию**
аргументом. Это основа множества приёмов: колбэки, стратегии, функции
сортировки. Функцию, которая принимает или возвращает другую функцию, называют
**функцией высшего порядка**.

```go play
package main

import "fmt"

// apply применяет переданную функцию ко всем элементам и печатает результат.
func apply(nums []int, f func(int) int) {
	for _, n := range nums {
		fmt.Print(f(n), " ")
	}
	fmt.Println()
}

func main() {
	data := []int{1, 2, 3, 4}
	apply(data, func(x int) int { return x * x }) // квадраты
	apply(data, func(x int) int { return x + 10 }) // +10
}
```

Вывод:

```
1 4 9 16 
11 12 13 14 
```

## Замыкания

**Замыкание** (closure) — это функция, объявленная внутри другой функции,
которая «захватывает» переменные из окружающего контекста и продолжает иметь к
ним доступ даже после того, как внешняя функция завершилась. Захватываются не
копии значений, а сами переменные — поэтому замыкание может их менять, и эти
изменения сохраняются между вызовами.

Классический пример — счётчик. Внешняя функция объявляет `count` и возвращает
маленькую функцию, которая при каждом вызове увеличивает его:

```go play
package main

import "fmt"

func makeCounter() func() int {
	count := 0
	return func() int {
		count++ // меняем переменную из внешней области
		return count
	}
}

func main() {
	next := makeCounter()
	fmt.Println(next()) // 1
	fmt.Println(next()) // 2
	fmt.Println(next()) // 3

	other := makeCounter()
	fmt.Println(other()) // 1 — у каждого счётчика своё состояние
}
```

Вывод:

```
1
2
3
1
```

Главное в последних строках: каждый вызов `makeCounter()` создаёт
**новую** переменную `count` и новое замыкание над ней. Поэтому `next` и `other`
независимы — у каждого свой счётчик. Это и делает замыкания удобным способом
«упаковать» состояние без объявления отдельной структуры.

## Частые ошибки

- **Игнорирование ошибки.** `res, _ := safeDiv(a, b)` молча выбрасывает ошибку.
  Если она была, в `res` лежит `0`, и дальше программа работает с этим нулём как
  с настоящим результатом. Отбрасывайте ошибку через `_`, только когда точно
  знаете, что она здесь невозможна.
- **«Голый return» в длинной функции.** Технически работает, но читать тяжело:
  непонятно, что вернулось. Именованные возвраты — для коротких функций.
- **Захват переменной цикла в замыкании (до Go 1.22).** Если внутри `for`
  создавать замыкания, они захватывают **одну и ту же** переменную цикла. В
  старых версиях Go все замыкания видели её финальное значение. Начиная с
  версии языка Go 1.22 (директива `go` в модуле) переменные, объявленные
  циклом через `:=`, создаются заново на каждой итерации. При `=` с заранее
  объявленной переменной она остаётся общей. В чужом коде вы ещё встретите
  обходной приём `v := v` внутри цикла.
- **Путаница `...` в объявлении и при вызове.** В сигнатуре `nums ...int` делает
  параметр variadic. При вызове `sum(s...)` раскрывает готовый срез `s` в
  аргументы. Это две разные роли одного синтаксиса.

## Попробуйте сами

Напишите `larger(a, b int) int`, которая возвращает большее из двух чисел. Вызовите её для `(2, 7)`, `(9, 3)` и `(4, 4)`. Затем сохраните `a := 2`, вызовите `larger(a, 7)` и напечатайте `a`: изменится ли исходная переменная?

Вторая часть: напишите `stats(nums ...int) (lo, hi int)`. Для непустого списка она возвращает наименьшее и наибольшее число. Проверьте `stats(5, 1, 9, 3)`.

### Проверка

Результаты вызовов — `7`, `9`, `4`; исходная `a` остаётся `2`.

```go
func larger(a, b int) int {
    if a > b {
        return a
    }
    return b
}

func stats(nums ...int) (lo, hi int) {
    lo, hi = nums[0], nums[0]
    for _, n := range nums {
        if n < lo {
            lo = n
        }
        if n > hi {
            hi = n
        }
    }
    return
}
```

При равенстве `larger` возвращает `b`, но числовой результат тот же. Параметры — копии аргументов; чтобы обновить `a`, результат нужно присвоить явно: `a = larger(a, 7)`. `stats(5, 1, 9, 3)` вернёт `1 9`. Пустой вызов `stats()` упадёт на `nums[0]`: такой функции нужна проверка `len(nums)` или ошибка в результате.

Почему функция называется `larger`, а не `max`? С Go 1.21 в языке есть встроенные `max` и `min`: `max(2, 7)` работает без всякого объявления. Своя функция с тем же именем «заслонит» встроенную, как переменная в блоке из прошлой главы. Это законно, но сбивает с толку читателя. По той же причине результаты `stats` названы `lo` и `hi`.

## Что дальше

Вы можете передавать параметры, возвращать несколько результатов и использовать функции как значения. Следующая глава — [указатели](/go-basics/book/pointers): там станет видно, как изменить переменную вызывающего, если простой копии аргумента недостаточно.

К полной обработке `error` вернёмся в [главе об ошибках](/go-basics/book/errors), к срезам в variadic-примерах — в [коллекциях](/go-basics/book/collections). Замыкания перед запуском через `go` полезно перечитать во [вводной главе о горутинах](/go-basics/book/goroutines-intro).
