Урок 4 из 8 · 25 мин · обновлено

Функции и обработка ошибок

Как создавать функции с параметрами и типами, возвращать несколько значений и почему в Go ошибки — это обычные значения, которые проверяют через if err != nil.

Функции

Функция — именованный кусок кода, который можно вызывать много раз. Вы уже пользовались готовыми, например fmt.Println(). Свою создают через func:

Go
func greet(name string) {
	fmt.Println("Здравствуйте,", name)
}

func add(a, b int) int {
	return a + b
}
  • в скобках — параметры с типами. Если у соседних параметров тип один, его пишут один раз: a, b int;
  • после скобок — тип результата. У greet его нет: она ничего не возвращает;
  • return возвращает результат туда, где функцию вызвали.

Функции объявляют вне main, а вызывают по имени:

Go
greet("Анна")
fmt.Println(add(2, 3))
Результат
Здравствуйте, Анна
5

Несколько результатов

Функция в Go может вернуть сразу несколько значений:

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

q, r := divide(17, 5)
fmt.Println(q, r)
Результат
3 2

Если одно из значений не нужно, вместо имени ставят подчёркивание _. Неиспользованная переменная в Go — ошибка компиляции, а _ говорит: «значение знаю, но оно мне не нужно».

Go
_, rest := divide(17, 5)
fmt.Println(rest)
Результат
2

Ошибки — это значения

В Go нет исключений, которые внезапно обрывают программу. Если функция может не сработать, она возвращает два значения: результат и ошибку типа error. Например, strconv.Atoi превращает текст в число:

Go
n, err := strconv.Atoi("42")
fmt.Println(n, err)

n, err = strconv.Atoi("сорок два")
fmt.Println(n, err)
Результат
42 <nil>
0 strconv.Atoi: parsing "сорок два": invalid syntax

nil означает «ничего»: ошибки нет. Поэтому после такого вызова сразу проверяют ошибку:

Go
age, err := strconv.Atoi(input)
if err != nil {
	fmt.Println("Введите число, а не", input)
	return
}
fmt.Println("Возраст:", age)

Запись if err != nil вы будете видеть в коде на Go постоянно. Сначала кажется, что её слишком много. Зато по коду всегда видно, где что-то может пойти не так и что программа будет делать в этом случае.

Свои ошибки

Свою функцию с ошибкой пишут так же: последним результатом объявляют error.

Go
func withdraw(balance, amount int) (int, error) {
	if amount > balance {
		return balance, errors.New("недостаточно средств")
	}
	return balance - amount, nil
}

b, err := withdraw(500, 800)
if err != nil {
	fmt.Println("Ошибка:", err)
}
fmt.Println(b)
Результат
Ошибка: недостаточно средств
500
  • errors.New("…") создаёт ошибку с текстом, для этого подключают пакет errors;
  • fmt.Errorf("…") делает то же, но позволяет вставить значения, как Printf;
  • если всё хорошо, вместо ошибки возвращают nil.

Попробуйте сами. Напишите функцию discount(price, percent int) (int, error). Она возвращает цену со скидкой, а если скидка меньше 0 или больше 100 — ошибку. Проверьте: discount(1000, 15) даёт 850, а discount(1000, 120) — ошибку.

Решение
Go
package main

import "fmt"

func discount(price, percent int) (int, error) {
	if percent < 0 || percent > 100 {
		return 0, fmt.Errorf("скидка %d%% вне диапазона 0–100", percent)
	}
	return price * (100 - percent) / 100, nil
}

func main() {
	fmt.Println(discount(1000, 15))

	price, err := discount(1000, 120)
	if err != nil {
		fmt.Println("Ошибка:", err)
		return
	}
	fmt.Println(price)
}
Результат
850 <nil>
Ошибка: скидка 120% вне диапазона 0–100

В шаблоне %% выводит сам знак процента. При ошибке функция возвращает 0, но вызывающий код сначала смотрит на err и только потом пользуется ценой.

Мини-тест: проверьте себя

Закрепите урок: несколько вопросов, разбор ответов — в конце.

  1. Как правильно объявить функцию, которая складывает два целых числа?

  2. Функция strconv.Atoi("сорок два") вернула два значения. Что в них будет?

  3. Что означает, что функция вернула ошибку, равную nil?

  4. Зачем в записи _, rest := divide(17, 5) стоит подчёркивание?