День 11 · Своя функция: объявление, параметры, возврат · страница 3 из 7

Функция без результата и ранний выход

Не всякая функция что-то считает. Бывает работа, у которой нет ответа: напечатать строку отчёта, вывести предупреждение, показать таблицу. Такая функция ничего не возвращает — и объявляется чуть короче.


Тип результата можно не писать

Ситуация. Строки сводки печатаются в одном и том же виде: «Склад A: 14 шт.». Мест печати много, формат один.

Код для чтения · разбираем, набирать не нужно
package main

import "fmt"

func printRow(name string, qty int) {
	fmt.Println("Склад", name+":", qty, "шт.")
}

func main() {
	printRow("A", 14)
	printRow("B", 30)
}

Что увидите.

Пример · только посмотреть, набирать не нужно
stagiaire@lab:~/gocourse/day11/scratch$ go run .
Склад A: 14 шт.
Склад B: 30 шт.

Как это читать. После круглых скобок в заголовке ничего не написано — значит, результата нет. Такая функция полезна не значением, а тем, что она делает: печатает. main — функция ровно того же вида.

Вызов такой функции — не выражение. Он стоит отдельной строкой, как команда, и никуда не присваивается. Если попытаться использовать его как значение, получится знакомое с первой страницы report() (no value) used as value.

Различать два вида функций проще всего по вопросу: «что я хочу получить обратно?» Ничего — значит, результата нет, функция что-то делает. Число, строку, ответ «да/нет» — значит, есть, и в заголовке после скобок стоит тип.

Есть и общее правило: функция, которая одновременно и считает, и печатает, удобна ровно один раз, а потом мешает. Посчитанное значение может понадобиться в другом месте — сложить, сравнить, записать в файл, — а печать к нему приклеена. Поэтому расчёт обычно возвращают, а печатают отдельно. Сегодняшние задания устроены именно так: boxes и cost только считают.


Попробуйте сейчас: три числа.

Цель: предсказать все три числа, которые напечатает программа с функцией без результата.

1. Не запуская — что напечатает программа? Три строки, в каждой по числу.

Код для чтения · разбираем, набирать не нужно
package main

import "fmt"

func pack(qty int) {
	qty = qty * 2
	fmt.Println("в функции:", qty)
}

func main() {
	qty := 3
	pack(qty)
	pack(qty)
	fmt.Println("в main:", qty)
}

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

2. Сдайте три числа через пробел, в том порядке, в каком они напечатаются:

▶ Выполните · выделенное замените своим
course answer day11.q3 ЧИСЛО ЧИСЛО ЧИСЛО

3. Проверьте запуском — наберите ту же программу в scratch/main.go:

✎ Наберите в файл scratch/main.go
package main

import "fmt"

func pack(qty int) {
	qty = qty * 2
	fmt.Println("в функции:", qty)
}

func main() {
	qty := 3
	pack(qty)
	pack(qty)
	fmt.Println("в main:", qty)
}
▶ Выполните
cd ~/gocourse/day11/scratch
go run .

Готово, когда: пункт q3 в course check 11 зелёный.


return без значения — досрочный выход

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

Код для чтения · разбираем, набирать не нужно
package main

import "fmt"

func check(qty int) {
	if qty == 0 {
		fmt.Println("Пусто - нечего отгружать")
		return
	}
	fmt.Println("Количество:", qty)
	fmt.Println("Готово к отгрузке")
}

func main() {
	check(0)
	check(3)
}

Что увидите.

Пример · только посмотреть, набирать не нужно
stagiaire@lab:~/gocourse/day11/scratch$ go run .
Пусто - нечего отгружать
Количество: 3
Готово к отгрузке

Как это читать. У return две работы, и на первой странице мы пользовались обеими сразу. В функции без результата остаётся только первая: закончить работу функции здесь. Значение не пишется — его и не ждут.

Разберём по вызовам:

Вызов Условие qty == 0 Что напечаталось Где закончилась функция
check(0) истина Пусто - нечего отгружать на return внутри if
check(3) ложь Количество: 3 и Готово к отгрузке на закрывающей }

Функция без результата заканчивается и сама, дойдя до конца тела, — писать return последней строкой не нужно.

Зачем так пишут. Тот же смысл можно выразить через else:

Код для чтения · разбираем, набирать не нужно
package main

import "fmt"

func check(qty int) {
	if qty == 0 {
		fmt.Println("Пусто - нечего отгружать")
	} else {
		fmt.Println("Количество:", qty)
		fmt.Println("Готово к отгрузке")
	}
}

func main() {
	check(0)
	check(3)
}
Пример · только посмотреть, набирать не нужно
stagiaire@lab:~/gocourse/day11/scratch$ go run .
Пусто - нечего отгружать
Количество: 3
Готово к отгрузке

Вывод тот же. Разница видна, когда проверок становится три или четыре: с else каждая добавляет уровень вложенности, и основная работа уезжает вправо; с ранним return проверки стоят одна за другой в начале, а дальше идёт ровный, ничем не сдвинутый основной случай. Такие проверки в начале называют стражами: они отсеивают случаи, в которых делать нечего.

В функции с результатом ранний выход выглядит так же, но значение обязательно: return 1, return 0. Именно так устроена программа в задании trace на следующей странице.


Попробуйте сейчас: сколько строк.

Цель: посчитать, сколько строк напечатает программа с ранним выходом, и сдать число.

1. Не запуская посчитайте, сколько всего строк напечатает программа:

Код для чтения · разбираем, набирать не нужно
package main

import "fmt"

func check(qty int) {
	fmt.Println("проверяю", qty)
	if qty == 0 {
		return
	}
	fmt.Println("ок", qty)
}

func main() {
	check(2)
	check(0)
	check(1)
	fmt.Println("конец")
}

Считайте все строки вывода, включая последнюю.

2. Сдайте число:

▶ Выполните · выделенное замените своим
course answer day11.q4 ЧИСЛО

3. Проверьте запуском — наберите ту же программу в scratch/main.go и сверьте, какие именно строки напечатались:

✎ Наберите в файл scratch/main.go
package main

import "fmt"

func check(qty int) {
	fmt.Println("проверяю", qty)
	if qty == 0 {
		return
	}
	fmt.Println("ок", qty)
}

func main() {
	check(2)
	check(0)
	check(1)
	fmt.Println("конец")
}
▶ Выполните
cd ~/gocourse/day11/scratch
go run .

Готово, когда: пункт q4 в course check 11 зелёный.


Что может пойти не так

Функция без результата, а return со значением:

Код для чтения · разбираем, набирать не нужно
package main

import "fmt"

func check(qty int) {
	if qty == 0 {
		return 0
	}
	fmt.Println("Количество:", qty)
}

func main() {
	check(3)
}
Пример · только посмотреть, набирать не нужно
stagiaire@lab:~/gocourse/day11/scratch$ go run .
# day11/scratch
./main.go:7:10: too many return values
	have (number)
	want ()

want () — пустые скобки: ждали ничего. Бывает это чаще всего по привычке из других языков, где return 0 значит «всё хорошо». В Go для этого есть коды возврата программы (os.Exit из дня 07), а return внутри функции к ним отношения не имеет.

Обратный случай — функция обещала результат, а в одной из веток вернула пустоту:

Код для чтения · разбираем, набирать не нужно
package main

import "fmt"

func boxes(qty int) int {
	if qty == 0 {
		return
	}
	return qty / 6
}

func main() {
	fmt.Println(boxes(12))
}
Пример · только посмотреть, набирать не нужно
stagiaire@lab:~/gocourse/day11/scratch$ go run .
# day11/scratch
./main.go:7:3: not enough return values
	have ()
	want (int)

Ранний выход в функции с результатом обязан что-то вернуть. Что именно — решает задача: для «коробок» разумно return 0. Вопрос «а что возвращать, когда данные плохие» в общем виде решается иначе — через второй результат типа error, и это тема завтрашнего дня.

Третий случай — строка, до которой выполнение не дойдёт. Если она стоит после return внутри if, программа собирается, а go vet печатает находку — как в дне 08 после break:

Пример · только посмотреть, набирать не нужно
stagiaire@lab:~/gocourse/day11/scratch$ go vet .
main.go:8:3: unreachable code

А вот строка, дописанная после последнего return в теле, даёт уже знакомое missing return: тело функции перестало заканчиваться возвратом.

Что видите Что это значит Что делать
too many return values и want () у функции нет типа результата, а return со значением убрать значение или дописать тип в заголовок
not enough return values и want (int) в одной из веток return без значения вернуть что-то осмысленное: 0, -1, пустую строку
missing return до конца тела return может не встретиться return в конце тела
report() (no value) used as value вызов функции без результата используют как значение вызывать отдельной строкой
unreachable code от go vet строки после return внутри функции удалить их или перенести выше
функция печатает, а не возвращает, и её результат нужен в другом месте расчёт и печать склеены вернуть значение, печатать снаружи

Дальше: куда уходит управление при вызове и как это записать таблицей — course next