Make или New? Вечный вопрос Go-разработчика
Вы пишете на Go уже неделю или год, но всё равно иногда зависаете на секунду перед выбором: make() или new()? Это нормально. Вопрос "Why would I make() or new()?" на Stack Overflow собрал десятки тысяч просмотров, и он не теряет актуальности даже в 2025 году. Почему? Потому что за кажущейся простотой скрывается фундаментальное различие в философии управления памятью в Go.
Давайте раз и навсегда разберемся, что к чему, чтобы вы больше не гадали. Мы пройдём от базы до реальных кейсов, подсветим ловушки и дадим чек-лист для ежедневной работы. Поехали!
Короткий ответ (для тех, кто спешит)
Если вам нужно выделить память под слайс (slice), мапу (map) или канал (channel) — используйте make(). Во всех остальных случаях — new() или литерал &Type{}. Но дьявол, как всегда, в деталях.
Что такое new() и как он работает
Функция new(T) — это встроенная функция Go, которая выделяет память под тип T, обнуляет её (zero value) и возвращает указатель на эту память: *T.
Проще говоря: new() даёт вам пустой, но валидный объект. Вы получаете указатель, но сам объект пока не готов к полноценной работе, если ему требуется инициализация внутренних структур.
package main
import "fmt"
type Person struct {
Name string
Age int
}
func main() {
p := new(Person)
fmt.Println(p) // Вывод: &{ 0}
fmt.Println(p.Name == "") // true
}
Здесь new(Person) выделил память под структуру, обнулил поля (пустая строка и 0) и вернул указатель. Всё честно.
Что такое make() и его суперсила
make() — это тоже встроенная функция, но она работает только с тремя типами: слайсы, мапы и каналы. Почему только они? Потому что эти типы имеют внутреннюю структуру (заголовок), которую нужно инициализировать, а не просто обнулить.
make() не возвращает указатель. Он возвращает инициализированное значение нужного типа (не указатель!).
// Слайс: длина 5, ёмкость 10
s := make([]int, 5, 10)
fmt.Println(len(s), cap(s)) // 5 10
// Мапа: готова к вставке элементов
m := make(map[string]int)
m["key"] = 42
fmt.Println(m) // map[key:42]
// Канал: буферизированный на 3 сообщения
ch := make(chan int, 3)
ch <- 1
fmt.Println(<-ch) // 1
Обратите внимание: если вы попробуете написать m := new(map[string]int), то получите указатель на nil-мапу. Вставка элемента в такую мапу вызовет панику (panic).
Почему new() не работает для слайсов, мап и каналов?
Давайте посмотрим на примере слайса. Слайс в Go — это структура из трёх полей: указатель на массив, длина и ёмкость. new([]int) выделит память под эту структуру и обнулит её. Указатель будет nil, длина и ёмкость — 0. Вы получите &[] — указатель на nil-слайс. Он не сломается при присваивании по индексу? Нет, упадёт.
make([]int, 3) же делает три вещи:
- Выделяет под капотом реальный массив из 3 элементов (или больше, если указана ёмкость).
- Инициализирует заголовок слайса (pointer, length, capacity).
- Возвращает готовый к использованию слайс.
То же самое с мапой: make(map[string]int) создаёт хеш-таблицу и выделяет память под бакеты. new(map[string]int) даёт только nil-мапу.
Когда использовать new() в 2025 году?
Современная практика в Go-сообществе такова: new() используется редко. Чаще всего разработчики предпочитают литералы с взятием адреса: &Type{}. Это более читаемо и позволяет сразу задать поля.
Пример, где new() всё ещё уместен:
// Внутри библиотеки, где нужен указатель на примитив
func NewIntPtr() *int {
return new(int) // *int со значением 0
}
// Альтернатива (более многословная):
func NewIntPtrV2() *int {
x := 0
return &x
}
Ещё один легальный кейс — работа с рефлексией (reflect), когда тип известен только в рантайме:
import "reflect"
t := reflect.TypeOf(42)
ptr := reflect.New(t) // создаёт *int
Но для 95% повседневных задач вам хватит &Struct{}.
Таблица сравнения: make vs new
Для быстрой ориентации держите шпаргалку:
| Параметр | new(T) | make(T, args) |
|---|---|---|
| Что возвращает | Указатель *T | Инициализированное значение T |
| Для каких типов | Любые (struct, int, string и т.д.) | Только slice, map, chan |
| Инициализация | Только zero value | Выделяет память и настраивает внутреннюю структуру |
| Можно ли использовать с литералом | Обычно заменяют на &Type{} | Не заменим (для map и slice есть литералы, но без длины) |
| Паника при работе | Да, если тип требует инициализации (map, slice) | Нет, всё готово к работе |
Подводные камни и частые ошибки
Ошибка 1: new(map[string]int) и попытка записи
m := new(map[string]int)
(*m)["key"] = 42 // PANIC: assignment to entry in nil map