Menu

Функции в Golang: объявление, вызов и передача функций

Как объявлять и вызывать функции в Go: параметры, общий тип для нескольких параметров, возвращаемые значения, передача по значению и функции как значения, которые можно хранить и передавать.

На этой странице есть исполняемые редакторы: меняйте, запускайте и сразу видите результат.

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

Объявление функции это 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), структура с конфигурацией, вариативные параметры или функциональные опции.

Coddy programming languages illustration

Учитесь программировать с Coddy

НАЧАТЬ