Почему ссылки на элементы слайса работают, а в мапе – нет? 🔥
Недавно на собеседовании мне задали, казалось бы, простой вопрос: "Почему из мапы мы не можем брать ссылки на значения из-за эвакуации, а из слайса можем? Ведь слайс тоже перевыделяется при расширении?"
Самое комичное в том, что собеседующим был Я!
Я задумался. Ведь действительно – и слайс, и мапа могут менять своё расположение в памяти при изменениях. Но слайсы почему-то позволяют брать ссылки на элементы, а мапа – нет.
Разобравшись, понял, что тут есть ключевое отличие, которое важно понимать каждому Gopher'у. Делюсь объяснением.
📌 Как устроены слайсы и мапы в Go?
🟢 Слайс – это обёртка над массивом
Когда мы создаём слайс, Go выделяет под него массив и хранит три вещи:
✅ Указатель на массив
✅ Длину (len
)
✅ Ёмкость (cap
)
При добавлении новых элементов (append
), если ёмкость позволяет, данные остаются в том же массиве. Если ёмкость заканчивается, Go:
🔹 Выделяет новый массив (обычно в 2 раза больше)
🔹 Копирует в него старые элементы
🔹 Обновляет указатель на массив
💡 Почему ссылки на элементы остаются рабочими?
👉 Пока не происходит перевыделения, элементы остаются по тем же адресам.
👉 Если перевыделение всё же случилось – старые ссылки станут недействительными, но это случается только при append'е.
s := []int{1, 2, 3} p := &s[1] // Ссылка на второй элемент fmt.Println(*p) // 2 s[1] = 42 fmt.Println(*p) // 42 (ссылка всё ещё работает!) s = append(s, 4, 5, 6) // Может случиться перевыделение fmt.Println(*p) // Всё ещё работает, если не было перевыделения!
⚠️ Важно: если слайс был расширен и перевыделился – старые ссылки больше невалидны.
🔴 А как ведёт себя мапа?
Мапа в Go устроена иначе. Это хеш-таблица с динамическим перераспределением элементов при изменениях.
👉 В отличие от слайса, элементы мапы не имеют фиксированных позиций в памяти.
👉 При изменениях (добавление/удаление ключей) Go может переместить элементы в новое место (эвакуация при росте или перераспределении).
📌 Почему нельзя брать ссылки на элементы мапы?
Go запрещает это на уровне компиляции, потому что при следующем изменении мапы элемент может переместиться, а ссылка будет указывать на "мертвую" область памяти.
m := map[string]int{"a": 1, "b": 2} p := &m["a"] // Ошибка: нельзя взять адрес элемента мапы
Go заранее предотвращает баги, запрещая брать ссылки на элементы мапы.
🚀 Вывод: почему из слайса можно, а из мапы – нет?
1️⃣ Слайс хранит элементы в массиве, и пока не происходит перевыделения – их адреса не меняются.
2️⃣ Мапа динамически перемещает элементы, поэтому Go запрещает ссылки на них, чтобы избежать UB.
- Слайс → Пока не делаешь
append
, ссылки валидны. - Мапа → Go не даёт брать ссылки вообще, потому что они сразу могут стать невалидными.
🔹 Поддерживаешь такой формат объяснений? Подписывайся, будем разбирать Go глубже! 🚀
Подписывайся на мой ТГ-канал https://t.me/gohired
Заходи в менторство https://teletype.in/@sashagolang/golang_mentor. У меня большой опыт свитча людей на го с других языков, так и захода с нуля.