Как получить срез ключей из map в Go: простое и эффективное решение
Почему в Go нет встроенной функции Keys()?
Вы правы — в Go 1.20 и ранее нет встроенной функции для получения ключей map. Это осознанное решение разработчиков языка: Go следует принципу явности (explicitness) и избегает "магии". Ручной перебор с for range — это идиоматичный, читаемый и понятный способ. Однако с выходом Go 1.21 ситуация улучшилась.
Основное решение: ручной перебор (Go 1.0+)
Это самый простой и прозрачный способ. Он работает во всех версиях Go:
package main
import "fmt"
func main() {
users := map[string]int{
"Alice": 25,
"Bob": 30,
"Charlie": 22,
}
// Получаем срез ключей
keys := make([]string, 0, len(users))
for k := range users {
keys = append(keys, k)
}
fmt.Println(keys) // [Alice Bob Charlie] (порядок не гарантирован!)
}
Важно: Порядок ключей в map не детерминирован в Go. Каждый запуск программы может дать разный порядок. Если нужен стабильный порядок — отсортируйте срез после получения:
import "sort"
// После получения keys:
sort.Strings(keys)
fmt.Println(keys) // [Alice Bob Charlie]
Современное решение: пакет maps (Go 1.21+)
Начиная с Go 1.21, в стандартной библиотеке появился пакет maps, который предоставляет функцию Keys():
import (
"fmt"
"maps"
)
func main() {
users := map[string]int{
"Alice": 25,
"Bob": 30,
"Charlie": 22,
}
keys := maps.Keys(users)
fmt.Println(keys) // [Alice Bob Charlie] (порядок не гарантирован!)
}
maps.Keys() возвращает срез ключей. Функция работает для любых типов map, где ключи — comparable типы (строки, числа, структуры без слайсов/мапов).
Универсальная функция-дженерик (Go 1.18+)
Если вы используете Go 1.18–1.20 и не хотите писать цикл каждый раз, создайте свою обобщённую функцию:
package main
import (
"fmt"
"golang.org/x/exp/maps" // или своя реализация
)
// Своя дженерик-функция
func Keys[K comparable, V any](m map[K]V) []K {
keys := make([]K, 0, len(m))
for k := range m {
keys = append(keys, k)
}
return keys
}
func main() {
strMap := map[string]int{"a": 1, "b": 2}
intMap := map[int]string{1: "one", 2: "two"}
fmt.Println(Keys(strMap)) // [a b]
fmt.Println(Keys(intMap)) // [1 2]
}
Что выбрать?
- Go 1.21+ — используйте
maps.Keys()из стандартной библиотеки. Это самый короткий и поддерживаемый способ. - Go 1.18–1.20 — напишите свою дженерик-функцию
Keys()или используйтеgolang.org/x/exp/maps. - Go 1.17 и ниже — только ручной перебор. Это нормально, так как код остаётся читаемым.
Совет: Всегда предварительно выделяйте память для среза через make([]K, 0, len(m)). Это повышает производительность, избегая лишних переаллокаций при append.
Альтернативный подход: использование reflect (не рекомендуется)
Технически можно получить ключи через пакет reflect, но я настоятельно не рекомендую этот способ в продакшене — он медленный, небезопасный и теряет информацию о типах:
import (
"fmt"
"reflect"
)
func main() {
m := map[string]int{"hello": 1, "world": 2}
v := reflect.ValueOf(m)
keys := v.MapKeys()
for _, k := range keys {
fmt.Println(k.Interface()) // hello, world (строка)
}
}
Почему это плохо:
- Огромные накладные расходы на рефлексию (в 10–100 раз медленнее)
- Потеря информации о типе — нужно делать type assertion
- Код становится нечитаемым и хрупким
Дополнение: работа с порядком ключей
Если вам нужны ключи в определённом порядке (например, по возрастанию), используйте пакет sort:
import "sort"
keys := maps.Keys(users) // Go 1.21+
sort.Strings(keys) // для строк
sort.Ints(keys) // для чисел
Для пользовательских типов используйте sort.Slice():
sort.Slice(keys, func(i, j int) bool {
return keys[i] < keys[j] // своя логика сравнения
})
Помните: ручной перебор — это не "велосипед", а идиоматичный Go-код. Он ясно показывает, что происходит, и не скрывает сложность за абстракциями.