Перейти до змісту

Функції

Функція в Go — це іменований (або анонімний) блок коду з типізованими параметрами та нулем чи більше типізованих значень повернення. Ключове слово — func. Ви вже бачили func main() як точку входу програми; ця стаття охоплює всі інші форми, яких може набувати функція.

Базова форма

func add(x int, y int) int {
    return x + y
}

fmt.Println(add(2, 3))     // 5

Три речі, на які варто звернути увагу:

  1. Оголошення починається з ключового слова func.
  2. Кожен параметр записується як ім'я типтип іде після імені, що є протилежним до C/Java.
  3. Тип повернення йде після списку параметрів, без : чи стрілки ->.

Коли кілька параметрів мають однаковий тип, можна вказати його один раз наприкінці:

func add(x, y int) int {        // same as (x int, y int)
    return x + y
}

Без значення повернення

Тип повернення можна не вказувати зовсім. Функція завершується, коли виконання доходить до кінця тіла (або зустрічає ранній return).

func greet(name string) {
    fmt.Println("Hello,", name)
}

Множинні значення повернення

Функція може повертати більше одного значення. Типи повернення беруться в дужки.

func divmod(a, b int) (int, int) {
    return a / b, a % b
}

q, r := divmod(7, 3)
fmt.Println(q, r)               // 2 1

Це основа угоди про обробку помилок у Go — канонічний підпис для операції, що може зазнати невдачі, має вигляд (результат, error):

func parseAge(s string) (int, error) {
    n, err := strconv.Atoi(s)
    if err != nil {
        return 0, err
    }
    return n, nil
}

Тип error розглядається в окремій статті (07-errors.md); поки що сприймайте його як «стандартний спосіб Go повідомити про помилку».

З досвіду Python: Python підтримує множинне повернення через розпакування кортежів (return a, b). Множинне повернення в Go є повноцінною частиною системи типів — підпис функції оголошує, що вона повертає два значення, компілятор перевіряє кожного виклику, і друге значення не можна втратити випадково.

Іменовані значення повернення

Можна надати значенням повернення імена прямо в підписі. Вони поводяться як заздалегідь оголошені змінні, ініціалізовані нульовими значеннями, а голий return (без виразу) повертає те, що в них зберігається наразі.

func split(sum int) (x, y int) {
    x = sum * 4 / 9
    y = sum - x
    return                      // returns x, y
}

fmt.Println(split(17))          // 7 10

Використовуйте іменовані значення повернення обережно. Вони найбільш корисні, коли:

  • Підпис інакше містить два значення одного типу, і імена роз'яснюють призначення ((width, height int)).
  • Тіло функції достатньо коротке, щоб неявне присвоєння було очевидним.

Для довших функцій надавайте перевагу явному return x, y — читачу не доведеться переглядати все тіло, щоб зрозуміти, що повертається.

Варіадичні параметри

Параметр типу ...T приймає нуль або більше аргументів і зв'язує їх як зріз []T всередині функції. Лише останній параметр може бути варіадичним.

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

fmt.Println(sum())              // 0
fmt.Println(sum(1, 2, 3))       // 6

xs := []int{1, 2, 3, 4}
fmt.Println(sum(xs...))         // 10 — spread an existing slice with ...

З досвіду Python: *args та **kwargs в одному — але з типами. Go не має еквівалента іменованих аргументів; якщо потрібні іменовані параметри, використовуйте структуру або патерн «функціональних опцій».

Функції є значеннями

Типи функцій є повноправними. Функцію можна присвоїти змінній, передати як аргумент або повернути з іншої функції.

func apply(f func(int) int, x int) int {
    return f(x)
}

double := func(n int) int { return n * 2 }
fmt.Println(apply(double, 5))   // 10

Тип func(int) int описує «функцію, яка приймає один int і повертає один int». Будь-яка функція з таким підписом може зайняти цей слот.

Анонімні функції та замикання

Функціональний літерал (без імені) можна визначити прямо на місці. Він також може замикатися над змінними з навколишнього контексту — захоплюючи посилання на них, а не копії.

func makeCounter() func() int {
    count := 0
    return func() int {
        count++
        return count
    }
}

c := makeCounter()
fmt.Println(c(), c(), c())      // 1 2 3

count живе стільки, скільки повернуте замикання на нього посилається, навіть після того, як сама makeCounter завершила виконання. Це ті самі семантики замикань, що й у вкладених функціях Python.

Негайно викликаний функціональний вираз

Поширений патерн із defer (розглядається в 11-defer.md): визначити функціональний літерал і одразу викликати його через ().

func() {
    fmt.Println("runs once, right now")
}()
// output: runs once, right now

Функції не мають синтаксичного цукру для необов'язкових параметрів

Go не підтримує іменовані аргументи, необов'язкові позиційні аргументи або значення за замовчуванням. Якщо вам потрібна така зручність, ідіоматичні варіанти:

  • Кілька функцій з описовими іменами (NewReader / NewReaderSize).
  • Параметр-структура (func Open(opts Options)).
  • Патерн «функціональних опцій» (func Open(opts ...Option)).

З досвіду Python: немає def f(x=42, *, debug=False). Ідіоматичний Go надає перевагу додатковому конструктору або структурі опцій замість іменованих аргументів. Спочатку це здається многослівним; швидко перестаєш помічати.

Рекурсія

Рекурсія дозволена і нічим не відрізняється від очікуваного. Go не виконує оптимізацію хвостових викликів — глибоко рекурсивна функція може вичерпати стек. Для більшості практичних глибин це несуттєво; стеки горутин починаються малими (~8 КБ) і ростуть за потреби.

func factorial(n int) int {
    if n <= 1 {
        return 1
    }
    return n * factorial(n-1)
}

fmt.Println(factorial(10))      // 3628800

Короткий довідник

Форма Виглядає як
Одне значення повернення func f(x int) int { ... }
Множинні значення повернення func f() (int, error) { ... }
Список параметрів одного типу func f(x, y, z int) { ... }
Іменовані значення + голий return func f() (n int, err error) { ... return }
Варіадична func f(xs ...int) { ... }; виклик: f(1, 2, 3) або f(slice...)
Функція як параметр func apply(f func(int) int, x int) int
Функціональний літерал func(x int) int { return x*x }
Замикання вкладений func, що захоплює зовнішню змінну

Джерела