«Go» паникует
Попробую рассказать ещё об одной вещи, которая показалась мне в «Go» странной, дополню предыдущий пост.
Иногда, скомпилированная программа, написанная на «Go» «паникует»:
bolk-osx:Sample bolk$ 6g t.go && 6l t.6 && ./6.out
panic: runtime error: index out of range
runtime.panic+0xac /Users/bolk/go/src/pkg/runtime/proc.c:1083
runtime.panic(0x11ea8, 0xf8400011f0)
runtime.panicstring+0xa3 /Users/bolk/go/src/pkg/runtime/runtime.c:116
runtime.panicstring(0x295e6, 0xce63)
runtime.panicindex+0x25 /Users/bolk/go/src/pkg/runtime/runtime.c:73
runtime.panicindex()
main.main+0x5e /Users/bolk/Проекты/Sample/t.go:6
main.main()
runtime.mainstart+0xf /Users/bolk/go/src/pkg/runtime/amd64/asm.s:77
runtime.mainstart()
runtime.goexit /Users/bolk/go/src/pkg/runtime/proc.c:150
runtime.goexit()
----- goroutine created by -----
_rt0_amd64+0x8e /Users/bolk/go/src/pkg/runtime/amd64/asm.s:64
То есть возникает ошибка, которая аварийно завершает выполнение программы. Самый простой способ этого добиться — вызов «panic», более реальный — обратиться за границу массива:
package main
func main() {
arr := []int{1,2,3}
print(arr[1000])
}
«Паника», конечно, ситуация исключительная, программист должен постараться не допускать ситуации, в которых она возникает, но всё же, программа «паникует», а нам нужно продолжить её выполнение, например, чтобы произвести какие-то завершающие действия. Что делать?
Я спросил пару человек, незнакомых с «Go», что, по их мнению, язык должен бы предоставить программисту для решения этой проблемы, оба человека назвали обработку исключений. К сожалению, тут исключений нет. Подход к решению этой проблемы у «Go» чем-то напоминает Visual Basic, а именно конструкцию On Error GoTo line.
В «Go» есть поистине замечательная штука — defer, с её помощью можно вызвать функцию когда управление покидает определённую зону видимости. Для языка без деструкторов (а в «Go» их нет) это очень удобно — можно закрывать файлы, сокеты и прочее в таком полуавтоматическом режиме — важно не забыть поставить нужный вызов только один раз.
Функции с defer будут вызваны когда управление покидает выбранную нами зону по любой причине, в том числе, когда произошла «паника». В функции можно проверить, была ли она вызвана нормальным ходом событий или что-то пошло не так, вызов этой проверочной инструкции (он называется «recover») «успокаивает панику».
Выглядит это вот так:
package main
func main() {
defer func() {
if r := recover(); r != nil {
print("Recovered")
}
}()
arr := []int{1,2,3}
print(arr[1000])
}
Происходит тут следующее: в зоне вызова функции «main» (тут, как в Си, при запуске вызывается «main») определена анонимная функция, которая тут же вызвана как defer, при возникновении ошибки (обращение за границу массива в предпоследней строке), управление покидает зону видимости функции «main» из-за чего вызывается наша анонимная функция, которая проверяет из-за чего она была вызвана (присваивает значение, полученное из recover, переменной „r“ и проверяет его на nil), чем «успокаивает панику», если она была, и выводит сообщение «Recovered» в этом случае.
что ж это за паники такие, после которых можно восстановится.
Комментарий для zg.livejournal.com:
Я привёл пример — выход за границу массива. Если хочется ещё пример, то вот этот код «запаникует» divizion by zero:
package main
import «big»
func main() {
a := big.NewInt(0)
b := big.NewInt(0)
print(a.Quo(a, b))
}
Метод Quo описан в документации: http://golang.org/pkg/big/#Int.Quo
Комментарий для Евгения Степанищева:
ну так вышли, падать надо, а не восстанавливаться.
Комментарий для zg.livejournal.com:
Не понял мысли. Зачем тут падать? Ну вышли, делов-то. Компилятор отловил ситуацию и как-то сообщил об этом программисту, падать-то зачем? Вот Пайтон же не падает:
bolk-osx:Sample bolk$ python -c «try: a=(); print a[2]
except: print(’Recover’)»
Recover
bolk-osx:Sample bolk$
Комментарий для Евгения Степанищева:
ну в питоне — это ж исключение, не?
есть места, где есть и исключения, и паники. исключения ловятся и используются в ситуациях: не хватило памяти, не смогли открыть файл, не удалось соединиться с удалённым хостом и т. д. паники приводят сразу к закрытию приложения. вызываться они могут в том числе и при выходе за границы массивы, при копировании строк, когда в строке назначении места не хватает, при доставании со стека больше, чем положили и т. д.
ведь обращение за пределы массива однозначно свидетельствует о логической ошибке в программе. зачем ей вообще работать с логической-то ошибкой?
Комментарий для zg.livejournal.com:
Исключение. Исключение в Пайтоне это не что-то исключительное, там оно сплошь и рядом, рекомендуется использовать их как можно более широко. Например, у строки есть метод, который порождает исключение, если подстрока не найдена.
Вот я как бы и намекаю на то, что из-за того, что в языке нет исключений, используется паника. Взглянем, например, на внутренности модуля regexp ( http://golang.org/src/pkg/regexp/regexp.go#L618 ):
618 func Compile(str string) (regexp *Regexp, error os.Error) {
619 regexp = new(Regexp)
620 // doParse will panic if there is a parse error.
621 defer func() {
622 if e := recover(); e != nil {
623 regexp = nil
624 error = e.(Error) // Will re-panic if error was not an Error, e. g. nil-pointer exception
625 }
626 }()
627 regexp.expr = str
628 regexp.inst = make([]*instr, 0, 10)
629 regexp.doParse()
630 return
631 }
Легко заметить, что тут «паника» используется ровно как исключение. Даже видно, что «паника» «пробрасывается» выше, если это не та ошибка, которую мы стремились подавить.
Причём «паника» она, как исключения, типизирована. Например: panic(42), panic(«Hello»), можно передавать структуры. То есть можно по типу и содержимому может что-то рассказывать об ошибке.
В Пайтоне я бы сделал как-то так:
try:
cached = cache[somekey]
except IndexError:
cached = None
Где тут логическая ошибка?
Или вот в моём примере про деление на ноль (вызов Quo), это такая страшная ситуация, что программа должна валиться и выходить?
Вообще, когда я читал документацию, у меня тоже сложилось впечатление, что «паника» — это unrecoverable fatal, теперь, когда я побольше почитал код модулей, я начинаю понимать, что «паника» это аналог исключений и относиться к ней нужно соответственно. Но обрабатывать её («панику») не слишком-то удобно.