История вопроса:
Go по умолчанию не имеет структуры Set, но часто возникает задача работать с уникальными элементами. Оптимальной структурой стал map[string]struct{}, где ключ — элемент, а пустая структура служит "меткой присутствия". Это общеупотребимый паттерн для быстрого membership-теста.
Проблема:
Недостаток встроенного Set приводит к тому, что новичкам кажется сложным правильно реализовать уникальные коллекции. Также нужно понять, почему struct{} эффективнее bool или int в качестве значения.
Решение:
Для реализации Set в Go используют map[string]struct{}. Пустая структура struct{} не требует памяти (zero-sized), а map обеспечивает быстрый доступ. Пример:
set := make(map[string]struct{}) set["foo"] = struct{}{} if _, ok := set["foo"]; ok { fmt.Println("Present") } delete(set, "foo")
Ключевые особенности:
Почему нельзя использовать slice/массив в качестве значения?
slice/mассив для set не даёт константного времени поиска элемента — придётся перебирать все значения, что медленно.
Чем отличается map[string]struct{} от map[string]bool?
map[string]bool занимает больше памяти: на каждый ключ хранится bool, а struct{} — пустой тип, не аллоцирует ничего.
set := map[string]bool{"foo": true}
Можно ли использовать int вместо struct{}?
Можно, но int всегда занимает память. struct{} универсален: если нужна только роль "метки" (присутствия), он лучше.
set := map[string]int{"foo": 1} // но хранит (ключ -> число)
Из-за незнания назначили map[string]bool для набора уникальных IP-адресов. В результате, при миллионах адресов памятипотребление выросло вдвое по сравнению с struct{}.
Плюсы:
Минусы:
В проекте для хранения уникальных email применили map[string]struct{}. Нагрузка уменьшилась, работало быстрее, память почти не тратится на значения.
Плюсы:
Минусы: