Avatar ·

Зачем в 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 типов:

  • map
  • slice
  • chan (каналы)

Эти типы имеют внутреннюю структуру (указатель на данные, 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()
mapmake(map[K]V) — ✅ работаетnew(map[K]V) — ❌ nil-карта
slicemake([]T, len, cap) — ✅new([]T) — ❌ nil-слайс
chanmake(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). Для