Опциональные параметры в Go: 5 рабочих способов (от простого к продвинутому)

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

«А где мои опциональные параметры?» — крик души Go-разработчика

Вы только что перешли на Go с Python или JavaScript и пишете функцию для отправки email. В Python это выглядело бы так:

def send_email(to, subject, cc=None, bcc=None, attachments=None):
    pass

Всё просто: cc, bcc и attachments — опциональные параметры. Их можно не передавать, и функция сработает со значениями по умолчанию.

Но вот незадача: в Go нет перегрузки методов и нет именованных аргументов с умолчаниями. Сообщество Stack Overflow подтверждает: вопрос «Optional Parameters in Go?» набирает тысячи просмотров. Это реальная боль новичков.

Хорошая новость: в Go есть как минимум 5 элегантных способов решить эту задачу. В этой статье мы разберём их все — от простых до промышленных.

Почему Go такой «строгий»?

Команда Go сознательно отказалась от опциональных параметров, чтобы:

  • Упростить чтение кода — вы всегда видите, сколько аргументов передаётся в функцию.
  • Избежать магического поведения — никаких неожиданных дефолтов.
  • Ускорить компиляцию — меньше синтаксического сахара.

Но это не значит, что вы не можете сделать код гибким. Давайте посмотрим, как обойти ограничения.

Способ 1: Вариативные функции (Variadic Functions) — «дешёво и сердито»

Самый простой способ — использовать троеточие ... для передачи переменного числа однотипных аргументов.

package main

import "fmt"

// Sum принимает любое количество целых чисел
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 — тоже работает
}

Плюсы: простота, встроенная поддержка языка.
Минусы: все аргументы должны быть одного типа. Нельзя сказать «передай строку, а потом опциональный int».

Вариативные функции в Go отлично подходят для логгеров, сумматоров и любых операций, где количество данных заранее неизвестно.

Способ 2: Структура конфигурации (Configuration Struct) — «когда параметров много»

Если у вас больше 2-3 опциональных параметров разных типов — используйте структуру.

package main

import "fmt"

type EmailConfig struct {
    CC          []string
    BCC         []string
    Attachments []string
    IsHTML      bool
}

func SendEmail(to, subject, body string, cfg EmailConfig) {
    fmt.Printf("To: %s, Subject: %s\n", to, subject)
    if cfg.IsHTML {
        fmt.Println("HTML mode enabled")
    }
    if len(cfg.CC) > 0 {
        fmt.Printf("CC: %v\n", cfg.CC)
    }
}

func main() {
    // Используем значения по умолчанию (zero values)
    SendEmail("user@example.com", "Hello", "Body", EmailConfig{})

    // Переопределяем некоторые поля
    cfg := EmailConfig{
        CC:     []string{"boss@example.com"},
        IsHTML: true,
    }
    SendEmail("user@example.com", "Hello", "Body", cfg)
}

Обратите внимание: zero values в Go (пустая строка, 0, nil для слайсов) становятся значениями по умолчанию. Это и есть наша реализация опциональных параметров в Go через структуры.

Плюсы: читаемо, типизировано, легко расширять.
Минусы: нужно создавать структуру каждый раз, даже если ничего не меняешь.

Способ 3: Functional Options Pattern — «золотой стандарт Go»

Этот паттерн придумал Дэйв Чейни, один из ключевых разработчиков Go. Он используется в таких библиотеках, как gRPC, AWS SDK, и собственной стандартной библиотеке Go (например, http.Server).

package main

import (
    "fmt"
    "time"
)

// Option — тип функции, которая меняет конфигурацию
type Option func(*Server)

// Server — наша структура с параметрами по умолчанию
type Server struct {
    Port    int
    Timeout time.Duration
    MaxConn int
}

// NewServer — конструктор с опциональными параметрами
func NewServer(opts ...Option) *Server {
    // Значения по умолчанию
    srv := &Server{
        Port:    8080,
        Timeout: 30 * time.Second,
        MaxConn: 100,
    }
    // Применяем все переданные опции
    for _, opt := range opts {
        opt(srv)
    }
    return srv
}

// WithPort — опция для установки порта
func WithPort(port int) Option {
    return func(s *Server) {
        s.Port = port
    }
}

// WithTimeout — опция для таймаута
func WithTimeout(t time.Duration) Option {
    return func(s *Server) {
        s.Timeout = t
    }
}

func main() {
    // Сервер с настройками по умолчанию
    srv1 := NewServer()
    fmt.Printf("Default: %+v\n", srv1)

    // Сервер с кастомным портом и таймаутом
    srv2 := NewServer(WithPort(9090), WithTimeout(60*time.Second))
    fmt.Printf("Custom: %+v\n", srv2)
}

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

Этот паттерн Functional Options — де-факто стандарт для Go-проектов, когда нужны опциональные аргументы в Go.

Способ 4: Builder Pattern — «понятно и цепочечно»

Если вам нужно собирать конфигурацию пошагово, используйте Builder.

package main

import "fmt"

type QueryBuilder struct {
    table   string
    where   string
    orderBy string
    limit   int
}

func NewQueryBuilder(table string) *QueryBuilder {
    return &QueryBuilder{table: table}
}

func (qb *QueryBuilder) Where(condition string) *QueryBuilder {
    qb.where = condition
    return qb
}

func (qb *QueryBuilder) OrderBy(field string) *QueryBuilder {
    qb.orderBy = field
    return qb
}

func (qb *QueryBuilder) Limit(n int) *QueryBuilder {
    qb.limit = n
    return qb
}

func (qb *QueryBuilder) Build() string {
    query := "SELECT * FROM " + qb.table
    if qb.where != "" {
        query += " WHERE " + qb.where
    }
    if qb.orderBy != "" {
        query += " ORDER BY " + qb.orderBy
    }
    if qb.limit > 0 {
        query += fmt.Sprintf(" LIMIT %d", qb.limit)
    }
    return query
}

func main() {
    q := NewQueryBuilder("users").
        Where("age                

Блоги

Рекомендаций по книгам