Послевкусие от «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.

8 комментариев

«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» в этом случае.

6 комментариев