Функції¶
Функція в Go — це іменований (або анонімний) блок коду з типізованими
параметрами та нулем чи більше типізованих значень повернення. Ключове
слово — func. Ви вже бачили func main() як точку входу програми;
ця стаття охоплює всі інші форми, яких може набувати функція.
Базова форма¶
Три речі, на які варто звернути увагу:
- Оголошення починається з ключового слова
func. - Кожен параметр записується як
ім'я тип— тип іде після імені, що є протилежним до C/Java. - Тип повернення йде після списку параметрів, без
:чи стрілки->.
Коли кілька параметрів мають однаковий тип, можна вказати його один раз наприкінці:
Без значення повернення¶
Тип повернення можна не вказувати зовсім. Функція завершується, коли
виконання доходить до кінця тіла (або зустрічає ранній return).
Множинні значення повернення¶
Функція може повертати більше одного значення. Типи повернення беруться в дужки.
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):
визначити функціональний літерал і одразу викликати його через ().
Функції не мають синтаксичного цукру для необов'язкових параметрів¶
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, що захоплює зовнішню змінну |
Джерела¶
- Оголошення функцій — go.dev/ref/spec#Function_declarations
- Типи функцій — go.dev/ref/spec#Function_types
- Функціональні літерали — go.dev/ref/spec#Function_literals
- Передача аргументів до параметрів
...— go.dev/ref/spec#Passing_arguments_to_..._parameters - Effective Go: Функції — go.dev/doc/effective_go#functions