Объявление функции
Объявление функции это func, имя, параметры, тип результата и тело. Тип пишется после имени параметра, а не перед ним.
add принимает два значения int и возвращает int. greet ничего не возвращает, поэтому в её сигнатуре нет типа результата, а в теле не нужен return.
Функция, объявившая результат, должна заканчиваться завершающей инструкцией. Если какой-то путь может дойти до закрывающей скобки без возврата, компилятор остановится с ошибкой missing return.
Параметры с общим типом
У идущих подряд параметров одного типа может быть одно общее имя типа. func add(a, b int) int означает ровно то же, что func add(a int, b int) int. Смешивать тоже можно:
func scale(x, y float64, factor int) (float64, float64)
Здесь x и y имеют тип float64, а factor имеет тип int.
Возвращаемые значения
Функция может возвращать ноль, одно или несколько значений. Несколько результатов пишутся в скобках, и вызывающий код получает их множественным присваиванием:
Программа печатает 3 2. Пара (value, error) это самое частое применение этой возможности в Go, и ей посвящена отдельная страница о нескольких возвращаемых значениях.
Аргументы копируются
Go передаёт каждый аргумент по значению. Функция работает с копией, поэтому присваивание параметру никогда не меняет переменную вызывающего кода.
Вывод:
{1 2}
{100 2}
[99 2 3]
Третья строка удивляет. Значение слайса это небольшой заголовок (указатель, длина, ёмкость). Копия указывает на тот же базовый массив, поэтому запись в s[0] меняет элемент вызывающего кода. Но append внутри функции может выделить новый массив, и вызывающий код этот новый заголовок никогда не увидит. Если функция увеличивает слайс, возвращайте новый слайс. С мапами логика та же: функция может добавить ключи, и вызывающий код их увидит, потому что значение мапы ссылается на общие данные.
Используйте параметр-указатель, когда функция должна изменить переменную вызывающего кода или когда значение это большая структура, которую не хочется копировать при каждом вызове.
Функции это значения
У функции есть тип, который записывается без имени: func(int, int) int. Функцию можно сохранить в переменную, положить в мапу или слайс и передать в другую функцию.
square это анонимная функция, присвоенная переменной. Анонимные функции могут также читать и менять переменные из окружающей области видимости, что делает их замыканиями.
Именованный функциональный тип делает сигнатуры читабельнее, когда одна и та же форма встречается во многих местах:
type Transform func(int) int
func apply(xs []int, f Transform) []int
Нулевое значение функционального типа это nil. Вызов nil-функции вызывает панику с invalid memory address or nil pointer dereference, поэтому проверяйте необязательные колбэки перед вызовом. Функциональные значения можно сравнивать только с nil, но не друг с другом.
Рекурсия
Функция может вызывать саму себя. Оптимизации хвостовых вызовов в Go нет, но стеки горутин растут по мере надобности, так что обычная глубина рекурсии не проблема.
for i := range 6 считает от 0 до 5 и требует Go 1.22 или новее.
Чего нет у функций в Go
- Нет перегрузки. Две функции в одном пакете не могут иметь одно имя, даже с разными параметрами.
- Нет аргументов по умолчанию и именованных аргументов. Каждый параметр передаётся при каждом вызове, по порядку. Структура с конфигурацией даёт именованные необязательные поля:
NewServer(Config{Port: 8080}). - Нет вложенных именованных функций. Внутри тела функции можно объявлять только анонимные функции, обычно присваивая их переменной.
Списки аргументов переменной длины поддерживаются через вариативные функции.
Экспортируемые и неэкспортируемые функции
Функция, имя которой начинается с заглавной буквы (Parse), экспортируется, и её можно вызывать из других пакетов. Имя со строчной буквы (parse) видно только внутри своего пакета. Ключевых слов public и private нет; регистр первой буквы и есть всё правило.
Частые ошибки
- Ожидание, что изменение параметра дойдёт до вызывающего кода. Присваивание параметру меняет только копию. Верните новое значение или примите указатель.
- append внутри функции без использования результата.
func add(s []int) { s = append(s, 1) }никак не влияет на длину слайса вызывающего кода. Напишитеfunc add(s []int) []intи используйтеs = add(s). - Неиспользуемые результаты. Go позволяет игнорировать возвращаемое значение.
go vetне отмечает выброшенную ошибку, а линтеры вродеerrcheck(входит вgolangci-lint) отмечают. Используйте_ =только тогда, когда игнорирование это осознанное решение.
Часто задаваемые вопросы
Как объявить функцию в Go?
Ключевое слово func, имя, список параметров с типами после имён и тип результата: func add(a, b int) int { return a + b }. Функция без результата не указывает тип, а функция с несколькими результатами перечисляет их в скобках: func divmod(a, b int) (int, int).
Go передаёт аргументы по значению или по ссылке?
Всегда по значению. Функция получает копию каждого аргумента. Для int или структуры копия не зависит от переменной вызывающего кода. Слайсы, мапы, каналы и указатели это небольшие значения, ссылающиеся на общие данные, поэтому изменения элементов, на которые они указывают, видны вызывающему коду, а переприсваивание самого параметра нет. Передавайте указатель (*T), когда функция должна изменить переменную вызывающего кода.
Можно ли передать функцию как параметр в Go?
Да. Функции это значения с типом вроде func(int) int. Объявите параметр такого типа и передайте любую функцию с подходящей сигнатурой, именованную или анонимную: func apply(xs []int, f func(int) int).
Поддерживает ли Go перегрузку функций или параметры по умолчанию?
Нет. Каждое имя функции в пакете должно быть уникальным, и каждый параметр нужно передавать при каждом вызове. Обычные замены: разные имена (NewServer, NewServerWithTLS), структура с конфигурацией, вариативные параметры или функциональные опции.