Как обрабатывать добавление элемента в мапу в Go если значение уже есть
Что проверяют
Знаете ли вы идиому comma ok и поведение мапы при чтении отсутствующего ключа. Вторая половина вопроса про конкурентность: что будет, если две горутины одновременно проверяют и добавляют один и тот же ключ.
Как отвечать
Начните с того, что мапа не сообщает о перезаписи: m[k] = v молча заменит старое значение. Решение зависит от нужной семантики:
- не перезаписывать:
if _, ok := m[k]; !ok { m[k] = v }. Проверка черезokобязательна: чтение отсутствующего ключа возвращает нулевое значение типа, и0или""неотличимы от сохранённого нуля; - накапливать: для счётчика проверка не нужна,
m[k]++работает сразу, отсутствующий ключ читается как0. Со срезами так же:m[k] = append(m[k], v), добавление кnil-срезу корректно; - вернуть ошибку: если дубликат нарушает бизнес-правило, функция возвращает
error, напримерfmt.Errorf("key %q already exists", k); - изменить поле структуры:
m[k].Count++приmap[string]Userне скомпилируется, элемент мапы не адресуем. Либо достать значение, изменить и записать обратно, либо хранить указателиmap[string]*User.
Мапа не потокобезопасна: одновременная запись, а также запись вместе с чтением из разных горутин роняют процесс с fatal error, и recover это не перехватывает. Проверку и запись делают под одним sync.Mutex, иначе между проверкой ok и присваиванием вклинится другая горутина. Для сценария «добавить, если нет» у sync.Map есть атомарный LoadOrStore: он возвращает текущее значение и флаг loaded.
Где спотыкаются
Проверяют наличие ключа как if m[k] != 0. Это ломается, как только ноль становится допустимым значением.
Пишут в nil-мапу: var m map[string]int без make нормально читается, но запись вызывает панику assignment to entry in nil map.
Берут мьютекс только на запись, а проверку делают без него. Гонка остаётся: go test -race её покажет, а в проде она проявится редкими дублями или падением процесса.