Зарегистрируйтесь, чтобы продолжить обучение

Variadic-функции Go: Функции

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

Чтобы этого избежать, в Go есть variadic-функции — функции с переменным числом аргументов. Они позволяют передавать сколько угодно значений одного типа. Под капотом это всё тот же срез ([]T), просто собранный компилятором.

Базовый пример: сумма чисел

package main

import "fmt"

// Sum принимает любое количество чисел и считает их сумму.
// nums внутри — это обычный []int.
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, 40)) // 100
    fmt.Println(Sum())               // 0 — пустой срез
}

Если не передать ни одного аргумента, nums станет пустым срезом ([]int{}), и цикл просто не выполнится.

Передача готового среза

Часто у нас уже есть []int, и мы хотим передать его в variadic-функцию. В этом случае нужен ... - оператор распаковки среза.

numbers := []int{5, 15, 25}

fmt.Println(Sum(numbers...)) // 45

Если забыть ..., компилятор сообщит об ошибке: cannot use numbers (type []int) as type int in argument to Sum.

Логирование

В логах мы часто пишем сообщение и дополнительные детали, которых может быть 0, 1 или больше. Variadic-функции подходят идеально.

// Log выводит сообщение и список деталей.
// details — []interface{}, значит сюда можно передать значения любых типов.
func Log(message string, details ...interface{}) {
    fmt.Printf("[LOG] %s | details: %v\n", message, details)
}

func main() {
    Log("Ошибка при подключении к БД")
    Log("Ошибка при запросе", 500, "timeout", "retrying...")
}

// [LOG] Ошибка при подключении к БД | details: []
// [LOG] Ошибка при запросе | details: [500 timeout retrying...]

SQL-запрос с фильтрами

Допустим, у нас есть функция, которая собирает условие WHERE из произвольного количества фильтров.

import "strings"

// BuildWhere объединяет фильтры через AND.
// Если фильтров нет — возвращает пустую строку.
func BuildWhere(conditions ...string) string {
    if len(conditions) == 0 {
        return ""
    }

    return "WHERE " + strings.Join(conditions, " AND ")
}

func main() {
    fmt.Println(BuildWhere("age > 18", "country = 'RU'"))
    // WHERE age > 18 AND country = 'RU'
    fmt.Println(BuildWhere())
    // ""
}

Здесь мы не ограничиваем разработчика: он может передать хоть один фильтр, хоть двадцать.

Несколько аргументов и variadic

Variadic-параметр всегда стоит последним в списке аргументов. До него можно указывать обычные параметры, а уже затем вариативные.

// SendEmail принимает обязательный адрес и список получателей в копии.
func SendEmail(to string, cc ...string) {
    fmt.Printf("Письмо: %s (копии: %v)\n", to, cc)
}

func main() {
    SendEmail("user@example.com")
    SendEmail("user@example.com", "boss@example.com", "hr@example.com")
}

// Письмо: user@example.com (копии: [])
// Письмо: user@example.com (копии: [boss@example.com hr@example.com])

Variadic в стандартной библиотеке

Многие знакомые функции устроены именно так. fmt.Println(a ...interface{}) печатает любое количество значений разных типов. А append(slice, elems ...T) добавляет к срезу сразу несколько элементов.

slice := []int{1, 2}
slice = append(slice, 3, 4, 5) // добавляем три элемента сразу
fmt.Println(slice)             // [1 2 3 4 5]

Под капотом: что делает компилятор

Когда мы вызываем:

fmt.Println(Sum(1, 2, 3))

Go превращает это в:

fmt.Println(Sum([]int{1, 2, 3}))

То есть variadic-параметр — это всегда срез. В этот момент создаётся новый срез длиной три, в него копируются все значения, а в функцию передаётся структура среза (pointer, length, capacity). Если у нас уже есть срез и мы пишем f(slice...), то ничего не копируется — передаётся тот же самый срез.

Пустые вызовы

fmt.Println(Sum())

Компилятор подставит:

fmt.Println(Sum([]int{}))

Это значит, что nums будет пустым срезом ([]int{}), а не nil.

Подводные камни

Внутри функции variadic-параметр всегда превращается в срез, поэтому работать с ним нужно как с []T. При передаче готового среза обязательно использовать оператор ..., иначе компилятор выдаст ошибку. И помнить, что в списке аргументов может быть только один variadic-параметр, и он всегда должен быть последним.

Итоги

Variadic-функции в Go — это способ передавать переменное количество аргументов. Они упрощают API функций, когда заранее неизвестно количество данных, и позволяют не писать десятки перегрузок. Такой подход активно используется в стандартной библиотеке — например, в функциях fmt и в append. По сути это обычный срез, собранный автоматически компилятором. Главное — помнить, что variadic-параметр всегда идёт последним и обрабатывается как []T.


Самостоятельная работа

Попрактикуемся в создании функций с переменным числом Аргументов.

Реализуйте функцию JoinStrings(parts ...string) string, которая объединяет переданные строки через пробел. Если строк нет, функция должна возвращать пустую строку.

Примеры вызова:

fmt.Println(JoinStrings("Go", "это", "круто")) // Go это круто
fmt.Println(JoinStrings())                     // ""
fmt.Println(JoinStrings("Привет"))             // Привет
Показать решение
package main

import "fmt"

func JoinStrings(parts ...string) string {
    if len(parts) == 0 {
        return ""
    }
    res := parts[0]
    for _, p := range parts[1:] {
        res += " " + p
    }
    return res
}

func main() {
    fmt.Println(JoinStrings("Go", "это", "круто")) // Go это круто
    fmt.Println(JoinStrings())                     // ""
    fmt.Println(JoinStrings("Привет"))             // Go это круто
}

Дополнительные материалы

  1. Go by Example — Variadic Functions

Для полного доступа к курсу нужен базовый план

Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.

Получить доступ
1000
упражнений
2000+
часов теории
3200
тестов

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов
Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff