Функции: вызов, multi-return, замыкания
В Go функция это первоклассный объект (можно положить в переменную, передать в другую функцию, вернуть из функции). При этом синтаксис намеренно беднее, чем в JS или Python: нет default-аргументов, нет именованных аргументов при вызове, нет перегрузки. Меньше способов сделать одно и то же, проще читать чужой код.
После этого урока ты не “запомнишь синтаксис” (он простой), а уложишь
4 идиомы: (value, error), named returns с defer, variadic, и
замыкание над переменной.
1. Объявление и вызов
Базовая форма:
func имя(параметры) типВозврата {
тело
}
Минимальный пример:
func add(a int, b int) int {
return a + b
}
func main() {
s := add(2, 3) // 5
}
Сахар: подряд идущие параметры одного типа можно сократить:
func add(a, b int) int { return a + b }
Если функция ничего не возвращает, возвращаемый тип опускается:
func greet(name string) {
fmt.Println("hi,", name)
}
Несколько возвратов перечисляются в скобках:
func minMax(xs []int) (int, int) {
return xs[0], xs[len(xs)-1] // упрощение, без проверки пустого
}
Вызов читается слева направо как присваивание (a, b) = f(...):
lo, hi := minMax([]int{1, 2, 3})
2. Multiple return и идиома (value, error)
Главная идиома Go: функция, которая может ошибиться, возвращает пару
(результат, error), а не бросает исключение.
func ParseAge(s string) (int, error) {
n, err := strconv.Atoi(s)
if err != nil {
return 0, fmt.Errorf("parse age %q: %w", s, err)
}
if n < 0 {
return 0, fmt.Errorf("negative age: %d", n)
}
return n, nil
}
Шаблон вызова:
age, err := ParseAge("30")
if err != nil {
// обработка
return err
}
// дальше работаем с age
Это многословно, и это сделано сознательно: каждый возможный сбой виден глазами в коде, не “куда-то прыгнул через try-catch”. Подробно про error и обёртывание в 11-errors-deep.
Bool-flag вместо error
Когда “не получилось” не ошибка, а просто отсутствие, идиома такая же,
но второй аргумент bool, не error:
m := map[string]int{"a": 1}
v, ok := m["b"] // 0, false
v, ok = m["a"] // 1, true
Это та же пара, синтаксис языка делает её первоклассной во встроенных
конструкциях (map, chan, type assertion).
3. Named returns
Можно дать имена возвращаемым значениям прямо в сигнатуре:
func DivMod(a, b int) (q, r int, err error) {
if b == 0 {
err = errors.New("division by zero")
return // q=0, r=0, err=...
}
q = a / b
r = a % b
return
}
Что меняется:
q,r,errуже объявлены в начале функции, со значением 0/""/nil;returnбез аргументов возвращает их текущие значения. Это называется naked return;- читается местами лучше (видно “имена” возвратов), местами хуже (надо следить за состоянием).
Когда использовать:
- В длинных функциях, не использовать. Naked return на 30-й строке, это головоломка: не очевидно что куда уходит.
- В коротких функциях с
defer, удобно: можно подменитьerrв defer, и это будет последнее значение.
Пример с defer:
func ReadConfig(path string) (cfg Config, err error) {
f, err := os.Open(path)
if err != nil {
return
}
defer func() {
if cerr := f.Close(); cerr != nil && err == nil {
err = cerr
}
}()
return parseConfig(f)
}
Здесь defer видит err через имя, может его изменить. Без named
returns пришлось бы писать руками var err error и тратить место.
4. Variadic функции
Если функция принимает переменное число аргументов одного типа,
последний параметр пишется с ...:
func Sum(xs ...int) int {
s := 0
for _, x := range xs {
s += x
}
return s
}
Sum(1, 2, 3) // 6
Sum() // 0
Sum([]int{1,2,3}...) // 6, разворачивание слайса
Внутри функции xs это []int. Передать существующий слайс с
... означает “не собирать новый, использовать этот”.
Каноничный пример из stdlib, fmt.Println:
func Println(a ...any) (n int, err error)
То есть Println("a", 1, true) это вызов с тремя аргументами,
обёрнутый в []any{"a", 1, true}.
Несколько обычных + variadic
Variadic должен быть последним:
func Logf(level string, format string, args ...any) {
fmt.Printf("[%s] "+format+"\n", append([]any{}, args...)...)
}
Logf("INFO", "user %s logged in from %s", "vasya", "tor")
append([]any{}, args...)... это идиоматичный способ “пробросить
variadic дальше”.
5. Функции как первоклассные значения
Функция в Go это полноценное значение. Можно:
- положить в переменную,
- передать в другую функцию,
- вернуть из функции,
- сравнить с
nil.
Сравнение функций между собой компилятор не разрешает (f == g,
ошибка), сравнить можно только с nil (проверить, что переменная
вообще что-то держит).
type IntFn func(int) int
double := func(x int) int { return x * 2 } // anonymous function
fmt.Println(double(3)) // 6
apply := func(f IntFn, x int) int { return f(x) }
apply(double, 5) // 10
func(...) ... без имени, это анонимная функция (или
функция-литерал). Может быть вызвана сразу:
result := func(x int) int { return x*x }(5) // 25
Это IIFE, в Go применяется редко, но в отладке и инициализации иногда удобно.
премиум-урок
Продолжение в премиуме: 1 200 ₽ в месяц или 10 000 ₽ в год. Вместе с ним серверные проверки задач и школьные токены AI-коуча.
Оплата пока не подключена: премиум и курсы открываются промокодом.