ПрограммированиеBackend разработчик

Опишите, как реализованы variadic функции (функции с переменным числом аргументов) в Go. Как их правильно объявлять, вызывать, и какие подводные камни могут быть при передаче срезов?

Проходите собеседования с ИИ помощником Hintsage

Ответ

Variadic-функции в Go позволяют принимать переменное количество аргументов одного типа благодаря синтаксису с троеточием .... Например:

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

Вызывать такую функцию можно с любым количеством аргументов:

result := sum(1, 2, 3, 4) // 10

Или передать срез с использованием синтаксиса ...:

numbers := []int{1,2,3} result := sum(numbers...) // 6

Тонкости:

  • В функции variadic-параметры видны как срез ([]T). Это значит, можно передать внутрь уже подготовленный срез.
  • Такие параметры всегда должны быть последними в списке параметров функции.
  • Если передать срез без ..., функция ожидает обычный параметр типа []T, а не variadic.

Вопрос с подвохом

Какой результат даст следующий код и почему?

func printInts(nums ...int) { fmt.Printf("%#v ", nums) } ints := []int{1, 2, 3} printInts(ints)

Многие ошибочно считают, что этот код скомпилируется, но на самом деле он вызовет ошибку компиляции:

Ошибка: нельзя передать срез без ... в variadic-параметр.

Правильно:

printInts(ints...) // OK

Примеры реальных ошибок из-за незнания тонкостей темы


История

На крупном проекте функцию логирования описали как func Log(msgs ...string). При передаче заранее подготовленного среза строк вызывали просто Log(strings), получая некорректный вывод лога или panic во время работы. Причина — отсутствие ..., так что функция воспринимала один аргумент типа []string, а не отдельные строки.


История

В одной из utility функций требовалось обработать параметры, среди которых был срез, и добавить к нему больше элементов внутри. Ожидали, что ... создаёт копию среза, а не передаёт ссылку — но внутри функции изменяли срез, что неожиданно влияло на вызывающую сторону, особенно если исходный срез был переиспользуемым буфером.


История

Разработчик реализовал функции агрегирования, используя variadic-параметр, но один из коллег неверно использовал синтаксис вызова через кастинг: sum(interface{}([]int)), что приводило к неявным ошибкам времени исполнения, тогда как правильно было бы использовать явно sum(slice...). Незнание нюансов вызывало неочевидные баги при массовых рефакторингах.