February 26

Почему ссылки на элементы слайса работают, а в мапе – нет? 🔥 

Недавно на собеседовании мне задали, казалось бы, простой вопрос: "Почему из мапы мы не можем брать ссылки на значения из-за эвакуации, а из слайса можем? Ведь слайс тоже перевыделяется при расширении?"

Самое комичное в том, что собеседующим был Я!

Я задумался. Ведь действительно – и слайс, и мапа могут менять своё расположение в памяти при изменениях. Но слайсы почему-то позволяют брать ссылки на элементы, а мапа – нет.

Разобравшись, понял, что тут есть ключевое отличие, которое важно понимать каждому 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. У меня большой опыт свитча людей на го с других языков, так и захода с нуля.