Go: что происходит, если изменять map во время обхода?

В гошке можно изменять map прямо во время range. Вот так вот, да! Без предупреждения, регистраций и смс.

Но у этого поведения есть несколько нюансов.

Например, удалять элементы во время обхода можно: users := map[int]string{ 1: "Кароси", 2: "Свежемороженый минтай", 3: "Ада Вонг", } for id, name := range users { if id == 2 { delete(users, id) } fmt.Println(id, name) }

Это допустимо. Удалённый элемент больше не будет доступен в map. Но есть и другой интересный момент.

Открываем ящик пандоры.

А если добавлять элементы?

users := map[int]string{ 1: "Кароси", 2: "Свежемороженый минтай", } for id, name := range users { fmt.Println(id, name) if id == 1 { users[3] = "Ада Вонг" } }

Появится ли новый элемент во время текущего обхода?

Возможно, да, а возможно, нет. Спецификация Go прямо говорит: если во время итерации добавить новый элемент, он может быть выдан при обходе, а может быть пропущен.

Поэтому строить логику на том, что новый элемент обязательно попадёт в текущий range, нельзя.

А что с порядком элементов?

вот так вот: for id, name := range users { fmt.Println(id, name) }

не гарантирует определённый порядок.

При разных запусках порядок может отличаться.

Если нужен предсказуемый результат, можно сначала собрать ключи: ids := make([]int, 0, len(users)) for id := range users { ids = append(ids, id) } slices.Sort(ids) for _, id := range ids { fmt.Println(id, users[id]) }

Теперь порядок будет определяться сортировкой ключей.

Важное отличие: конкурентный доступ

Изменять map во время обычного range в одной goroutine можно с учётом описанных правил.

Но если одна goroutine читает map, а другая одновременно изменяет её без синхронизации, это уже другая ситуация.

Такой код небезопасен: go func() { users[4] = "Коулун" }() for id, name := range users { fmt.Println(id, name) }

Для конкурентного доступа нужна синхронизация, например sync.Mutex, либо подходящая для задачи sync.Map.

Что стоит запомнить?

Удалять элементы во время range можно.

Добавленные элементы могут попасть в текущий обход, а могут и не попасть. Порядок обхода map не гарантирован.

Конкурентное чтение и изменение без синхронизации небезопасно.

range по map не создаёт снимок её содержимого.

Поэтому если нужен стабильный набор элементов для обработки, лучше сначала получить отдельный список ключей, а уже потом работать с ним.