Послевкусие от «Go»
Должен сказать, что у «Go», как, наверное, у любого нового языка программирования, в изобилии свои, модные, ни на что не похожие тараканы. Небольшой пример приведу перед сном.
У «Go» есть хеши, называются «map», описываются несколько необычно: нужно описать не только тип значений, но и тип ключей. «Go» язык строгий, для него это важно:
package main
import `fmt`
func main() {
arr := map [string] byte { `a`: 1, `b`: 2}
fmt.Println(arr)
}Эта простенькая программка выведет на экран что ожидается: «map[a:1 b:2]», то есть строковое представление нашей переменной типа «map». Давайте представим, что из переменной «arr» нужно удалить
Можете прочитать руководство и попробовать найти нам функции «del», «delete», «remove», «destroy», что угодно, ничего такого вы там не найдёте. Попытка присвоить «nil» ни к чему не приведёт — язык не позволит это сделать. Что делать?
Можно, конечно, пересоздать переменную с новым значениями, но есть способ… э… лучше. Почему я в этом не уверен будет понятно дальше.
Каждый элемент переменной типа «map» содержит специальную магию. Если запросить у элемента не одно, а два значения, то вернутся два — значение элемента и флаг, существует ли он:
val, exists := arr["a"]
fmt.Println(val, exists) // выведет «1 true»Чтобы удалить значение из хеша надо присвоить второму значению «false»:
arr["a"] = arr["a"], false // a["a"][1] = false не сработает, у него другой смысл
fmt.Println(arr) // выдаёт map[b:2]
val, exists = arr["a"]
fmt.Println(val, exists) // выведет «0 false», значение сброшено и «не существует»Если запросить у хеша значение с ключом, которого никогда в нём не было, эффект будет тот же — нулевое или эквивалентное ему значение и «false», тут всё логично.
Выбор такого решения для удаления значения мне кажется странным.
На всякий случай, приведу место из официального руководства, где это всё описано, чтобы не возникло ощущение, что я просто его прочитал невнимательно:
Similarly, if an assignment to a map has the special form a[x] = v, ok and boolean ok has the value false, the entry for key x is deleted from the map; if ok is true, the construct acts like a regular assignment to an element of the map.
«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»
В «Go» есть поистине замечательная штука — defer, с её помощью можно вызвать функцию когда управление покидает определённую зону видимости. Для языка без деструкторов (а в «Go» их нет) это очень удобно — можно закрывать файлы, сокеты и прочее в таком полуавтоматическом режиме — важно не забыть поставить нужный вызов только один раз.
Функции с defer будут вызваны когда управление покидает выбранную нами зону по любой причине, в том числе, когда произошла «паника». В функции можно проверить, была ли она вызвана нормальным ходом событий
Выглядит это вот так:
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»