Как правильно работать с map и структурой в Go, чтобы избежать паники
Что проверяют
Знаете ли вы, какие операции с map в Go действительно паникуют, а какие безопасны. Следом обычно дают код с map структур или с горутинами и просят найти ошибку.
Как отвечать
Разложите по операциям, это сразу показывает понимание:
- чтение из nil map: безопасно, возвращает нулевое значение.
len,rangeиdeleteна nil map тоже не паникуют; - запись в nil map: паника
assignment to entry in nil map. Map нужно создать черезmake(map[K]V)или литералmap[K]V{}; - проверка ключа: идиома
v, ok := m[k]. Без неё не отличить отсутствие ключа от сохранённого нуля; - map внутри структуры: поле-map у
var s StoreилиStore{}равно nil. Решение: конструкторNewStore()или ленивая инициализация в методе с pointer receiver; - map структур:
m[k].Count++не скомпилируется, значения map неадресуемы. Либо копия, правка и запись обратно, либоmap[K]*T; - map указателей:
m[k].Countпри отсутствующем ключе обратится к nil и дастnil pointer dereference. Здесь проверкаokобязательна; - type assertion: в
map[string]anyзаписьv.(int)без второго значения паникует при несовпадении типа, используйтеn, ok := v.(int).
Отдельный пункт про конкурентность. Одновременная запись в map из нескольких горутин или запись параллельно с чтением даёт fatal error: concurrent map writes. Это не паника, а аварийное завершение рантайма, recover его не перехватит. Защита: sync.Mutex или sync.RWMutex рядом с map в структуре, либо sync.Map для сценария «много чтений, ключи редко меняются». Найти проблему помогает go test -race.
Где спотыкаются
Проверяют map на nil перед каждым чтением, думая, что иначе будет паника. Это лишний код и признак, что вы не знаете семантику.
Надеются на recover при гонке за map. Процесс всё равно упадёт, и чинить надо синхронизацию, а не ловить последствия.
Отдают внутреннюю map структуры наружу через геттер. Вызывающий код получает ту же map, пишет в неё без мьютекса и ломает инварианты. Возвращайте копию, например через maps.Clone.