Функция без результата и ранний выход
Не всякая функция что-то считает. Бывает работа, у которой нет ответа: напечатать строку отчёта, вывести предупреждение, показать таблицу. Такая функция ничего не возвращает — и объявляется чуть короче.
Тип результата можно не писать
Ситуация. Строки сводки печатаются в одном и том же виде: «Склад 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.gopackage 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.gopackage 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