Зачем в Go нужны функции make() и new() и в чем их разница?
Всем привет! Я только начал изучать Go и запутался в двух встроенных функциях — make() и new(). В документации написано, что обе выделяют память, но я не понимаю, когда какую использовать.
Вот простой пример, который меня смущает. Я хочу создать карту (map) и слайс (slice):
// Вариант 1: с new()
m := new(map[string]int)
s := new([]int)
// Вариант 2: с make()
m2 := make(map[string]int)
s2 := make([]int, 5)
Первый вариант компилируется, но при попытке добавить значение в карту через m["ключ"] = 1 я получаю panic: assignment to entry in nil map. Со слайсом тоже беда — s[0] = 10 падает с nil pointer. А второй вариант работает отлично.
В чем тут магия? Почему new() не работает для map и slice? Я читал, что new() возвращает указатель, а make() — нет, но это не объясняет, почему один работает, а другой нет. Объясните, пожалуйста, для новичка: когда использовать make(), а когда new()? И есть ли случаи, где new() действительно нужен?
Простое правило: make() — для слайсов, карт и каналов; new() — для структур и примитивов
Вы столкнулись с классической путаницей новичков в Go. Давайте разберемся по порядку.
Ключевое отличие:
make()— инициализирует (создает готовую к использованию внутреннюю структуру).new()— выделяет память и обнуляет (возвращает указатель на нулевое значение).
Почему ваш код падает?
Когда вы пишете m := new(map[string]int), Go выделяет память для указателя на map и заполняет его нулевым значением — то есть nil. Вы получаете *map[string]int (указатель на nil-карту). При попытке записать в nil-карту происходит паника.
То же самое со слайсом: new([]int) возвращает указатель на nil-слайс (*[]int), у которого нет underlying-массива.
А make() делает магию:
// make(map[string]int) создает:
// 1. Выделяет память под хеш-таблицу
// 2. Инициализирует внутренние bucket'ы
// 3. Возвращает готовый map[string]int (не указатель!)
m := make(map[string]int)
m["ключ"] = 42 // работает!
Когда что использовать?
1. make() — только для 3 типов:
mapslicechan(каналы)
Эти типы имеют внутреннюю структуру (указатель на данные, len, cap для слайса; хеш-таблица для map; буфер для канала). make() инициализирует эту структуру.
s := make([]int, 3, 5) // слайс длины 3, емкости 5
m := make(map[string]int) // пустая карта
ch := make(chan int, 10) // буферизированный канал
2. new() — для структур и примитивов:
new(T) возвращает *T (указатель на T), где T — любой тип. Память обнуляется.
type Person struct {
Name string
Age int
}
p := new(Person) // *Person, поля пустые: Name="", Age=0
n := new(int) // *int, значение 0
ptr := new([3]int) // *[3]int, массив из нулей
Но ведь make() не возвращает указатель!
Да, и это фича. В Go слайсы, карты и каналы — это ссылочные типы. Когда вы передаете map в функцию, вы не копируете всю хеш-таблицу, а передаете ссылку на нее. Поэтому нет смысла делать *map — достаточно просто map.
Сравните:
// Плохо: лишний указатель
m1 := new(map[string]int)
(*m1)["a"] = 1 // нужно разыменовывать, и это все еще nil-карта!
// Хорошо: просто map
m2 := make(map[string]int)
m2["a"] = 1 // чисто и понятно
Когда new() реально нужен?
- Структуры с большим количеством полей, которые вы хотите вернуть как указатель:
func NewPerson(name string) *Person {
p := new(Person)
p.Name = name
return p
}
- Синтаксический сахар — иногда короче, чем
&Person{}:
p1 := new(Person) // коротко
p2 := &Person{} // тоже работает, но длиннее
// Оба варианта идентичны по результату
Итоговая шпаргалка:
| Тип | make() | new() |
|---|---|---|
| map | make(map[K]V) — ✅ работает | new(map[K]V) — ❌ nil-карта |
| slice | make([]T, len, cap) — ✅ | new([]T) — ❌ nil-слайс |
| chan | make(chan T, buf) — ✅ | new(chan T) — ❌ nil-канал |
| struct | ❌ не применимо | new(Struct) — ✅ указатель |
| int, string и т.д. | ❌ не применимо | new(int) — ✅ указатель |
Запомните: make() — это "создай и приготовь к работе", new() — это "выдели память и обнули". Для map/slice/chan всегда используйте make(), если только вы не хотите получить nil-ссылку специально (например, для ленивой инициализации).
Дополнение: почему так сделали в Go?
На самом деле, путаница возникает из-за того, что в Go решили сделать три разных поведения для выделения памяти, а не одно универсальное, как в C или Java.
Историческая справка: В ранних версиях Go (до 1.0) была только new(), и она пыталась делать всё. Но разработчики поняли, что для map/slice/chan нужна инициализация внутренней структуры (выделение bucket'ов, создание underlying-массива), а не просто обнуление памяти. Так появилась make().
Альтернативный взгляд: Некоторые Go-разработчики вообще не используют new() в production-коде. Вместо этого они пишут:
// Вместо new(Person)
p := &Person{Name: "Alice"}
// Вместо new(int)
var x int // или x := 0
И это абсолютно нормально! new() — это пережиток C-стиля. В современном Go чаще используют литералы структур (&Struct{}) или объявления через var.
Практический совет: Если вы сомневаетесь, какой тип использовать — просто запомните:
- Для слайсов, карт и каналов — только
make(). - Для всего остального —
&T{}илиvar t T. new()используйте, только если пишете обобщенный код с дженериками (Go 1.18+) или работаете с рефлексией.
Кстати, вы можете проверить тип возвращаемого значения с помощью fmt.Printf("%T", m). Для