Как объявить константный массив (срез) в Go?
Почему константные массивы не работают в Go?
В Go константы — это действительно константы времени компиляции. Они могут быть только простыми значениями: числами, строками или булевыми литералами. Массивы и срезы являются составными типами данных, которые требуют вычислений во время выполнения (выделение памяти, инициализация), поэтому они не могут быть объявлены через const. Это фундаментальное ограничение языка.
Правильный подход: используйте переменные с защитой от изменений
Лучший способ получить "константный" массив — объявить его как переменную (var) на уровне пакета. Чтобы предотвратить случайное изменение, используйте следующие практики:
1. Массив фиксированного размера
package main
var DaysOfWeek = [7]string{
"Понедельник",
"Вторник",
"Среда",
"Четверг",
"Пятница",
"Суббота",
"Воскресенье",
}
func main() {
// Чтение работает
println(DaysOfWeek[0])
// Если кто-то попытается изменить — это его ответственность
// DaysOfWeek[0] = "Понедельник_измененный" // раскомментируйте — и это сработает
}
2. Срез (slice) с помощью функции init()
Если вам нужен динамический срез, который не должен меняться, используйте функцию init() для инициализации и не экспортируйте его напрямую:
package main
var primes []int
func init() {
primes = []int{2, 3, 5, 7, 11, 13}
}
// GetPrimes возвращает копию среза, чтобы защитить оригинал
func GetPrimes() []int {
result := make([]int, len(primes))
copy(result, primes)
return result
}
func main() {
p := GetPrimes()
println(p[0]) // 2
// p[0] = 100 // это изменит только копию, оригинал останется нетронутым
}
3. Использование unexported переменной + геттер (рекомендуемый способ)
package mypackage
// Константный срез на уровне пакета (не экспортируется)
var defaultColors = []string{"red", "green", "blue"}
// Exported геттер возвращает копию
func DefaultColors() []string {
c := make([]string, len(defaultColors))
copy(c, defaultColors)
return c
}
// Если нужен доступ к конкретному элементу
func DefaultColor(index int) string {
if index < 0 || index >= len(defaultColors) {
return ""
}
return defaultColors[index]
}
Почему это не const, но работает как константа?
- Соглашение об именовании: в Go принято называть такие "константы" с заглавной буквы (если они публичные) или с маленькой (если приватные).
- Защита через геттеры: возвращая копию среза, вы гарантируете, что оригинал не изменится.
- Использование init(): если вам нужно сложное вычисление при старте программы.
Важное замечание про производительность
Если вы используете массивы фиксированного размера (не срезы), компилятор может оптимизировать их почти как константы. Например:
var Digits = [10]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
Go компилятор может разместить такой массив в read-only секции памяти (в зависимости от версии и платформы), что делает его фактически неизменяемым на уровне памяти.
В итоге: в Go нет синтаксиса const для массивов, но есть проверенные паттерны, которые дают тот же эффект. Используйте их в своём коде!
Альтернативный подход: генерация кода или использование глобальных констант через map
Если вам нужно именно "константное" поведение на уровне компиляции, можно применить кодогенерацию. Например, написать скрипт, который создаёт Go-файл с объявлением массива через var и init(). Это не даст синтаксической защиты, но позволит централизованно управлять значениями.
Использование map как "константного" хранилища
package main
var StatusCodes = map[int]string{
200: "OK",
404: "Not Found",
500: "Internal Server Error",
}
// Геттер для безопасного доступа
func StatusText(code int) string {
return StatusCodes[code]
}
func main() {
println(StatusText(200))
// StatusCodes[200] = "Changed" // можно изменить, но это явное нарушение
}
Продвинутый вариант: использование замыканий
package main
func NewImmutableSlice(values ...int) func(int) int {
data := make([]int, len(values))
copy(data, values)
return func(index int) int {
if index < 0 || index >= len(data) {
return 0 // или panic
}
return data[index]
}
}
func main() {
get := NewImmutableSlice(10, 20, 30)
println(get(1)) // 20
// get(1) = 50 // так нельзя, это функция, а не переменная
}
Этот подход даёт полную защиту от изменений, но менее удобен для итерации. Выбирайте его, если безопасность данных критична, а производительность не главное.