Почему пустая структура подходит для реализации множества через map в Go?
Пустая структура подходит как значение в map-множестве, потому что она не содержит данных: важен только факт наличия ключа. Поэтому обычно используют map[T]struct{} и проверяют ключ вторым результатом чтения из map.
Значение struct{}{} не кодирует дополнительное состояние, а его размер равен нулю. При этом сама map всё равно хранит ключи и имеет внутренние накладные расходы, поэтому это не означает полного отсутствия памяти на элемент.
В Go нет отдельного встроенного типа множество. Для представления набора уникальных значений используют map, где ключом становится элемент множества, а значение служит только маркером присутствия.
Пустая структура решает исходную проблему без фиктивного полезного значения: в отличие от bool, она не добавляет семантику «истина или ложь», когда требуется лишь наличие ключа.
Если выбрать map[T]bool, чтение значения может быть неоднозначным для модели данных: ключ может отсутствовать или присутствовать со значением false. Для проверки членства всё равно приходится учитывать второй результат чтения из map.
При использовании map[T]struct{} значение не пытаются трактовать как состояние. Наличие ключа определяется только специальным признаком успешного поиска, поэтому случайно записать «ложный» маркер невозможно.
Чтение из map в двухзначной форме возвращает значение и признак наличия ключа. Для множества значение игнорируют, а второй результат используют как ответ на вопрос о членстве.
Добавление выполняется присваиванием пустого значения, удаление — встроенной операцией delete. Повторное добавление того же ключа не создаёт дубликат, поскольку map хранит каждый ключ не более одного раза.
По сравнению с map[T]bool вариант с пустой структурой точнее выражает намерение и обычно не требует хранения отдельного значения. Однако он не подходит, если для каждого ключа нужно хранить состояние, например признак активности, счётчик или дату.
Тип ключа должен быть сравнимым, поскольку map использует сравнение ключей для поиска. Пустая структура как значение сравнима и не содержит данных, но это свойство само по себе не делает произвольный тип ключа допустимым.
Сервису нужно быстро проверять, имеет ли пользователь доступ к экспериментальной функции. Вариант с []UserID потребовал бы линейного поиска и допускает дубликаты, а map[UserID]bool добавил бы значение, хотя требуется только членство.
Выбран map[UserID]struct{}: проверка и добавление выполняются за ожидаемое постоянное время, дубликаты исключаются структурой map, а тип явно показывает, что набор не хранит дополнительных данных. Если позднее понадобится хранить дату выдачи доступа, map-множество следует заменить на map[UserID]AccessInfo, не пытаясь расширять смысл пустого значения.
Дополнительный вопрос 1: достаточно ли проверить только значение, полученное при чтении из map?
Нет. Нулевое значение типа значения map возвращается и при отсутствии ключа. Для map[T]struct{} само значение неинформативно, поэтому нужно использовать второй результат — признак наличия ключа.
Дополнительный вопрос 2: гарантирует ли пустая структура отсутствие затрат памяти на запись в map?
Нет. У пустой структуры нет полезного payload и её размер равен нулю, но map хранит ключи, служебные данные и организационные структуры. Поэтому корректнее говорить об отсутствии отдельного содержимого значения, а не о бесплатном элементе.
Дополнительный вопрос 3: когда map[T]bool всё же лучше множества на пустых структурах?
map[T]bool лучше, когда ключу действительно соответствует двусоставное состояние, которое нужно сохранять: например, пользователь известен системе, но доступ ему запрещён. Если требуется только ответ «ключ входит в набор», map[T]struct{} обычно точнее передаёт модель данных.