GoJuniorTechnical

В чём разница между make() и new() в Go?

new(T) выделяет обнулённую память для любого типа и возвращает *T. make() инициализирует внутреннее состояние только для slice, map и channel, возвращая само значение, а не указатель.

make() vs new() в Go

Обе функции выделяют память, но для принципиально разных целей. new(T) выделяет память под нулевое значение типа T и возвращает указатель *T. make(T, args) создаёт и инициализирует внутренние структуры данных для трёх специальных встроенных типов: слайсов, мап и каналов — и возвращает само значение (не указатель).

new() — выделение памяти с нулевым значением

package main

import "fmt"

func main() {
    // new(int) возвращает *int, указывающий на int == 0
    p := new(int)
    fmt.Println(*p) // 0
    *p = 42
    fmt.Println(*p) // 42

    // Эквивалентно:
    var i int
    p2 := &i
    fmt.Println(*p2) // 0

    // new со структурой
    type Point struct{ X, Y int }
    pt := new(Point)
    fmt.Println(pt)  // &{0 0}
    pt.X = 10
    fmt.Println(*pt) // {10 0}
}

make() — инициализация slice, map, channel

package main

import "fmt"

func main() {
    // Slice: make([]T, len, cap)
    s := make([]int, 3, 10)
    fmt.Println(s, len(s), cap(s)) // [0 0 0] 3 10

    // Map: make(map[K]V) или make(map[K]V, initialCapacity)
    m := make(map[string]int, 100) // предварительно выделяет bucket'ы для ~100 элементов
    m["a"] = 1
    fmt.Println(m) // map[a:1]

    // Channel: make(chan T) или make(chan T, bufferSize)
    ch := make(chan string, 5) // буферизованный канал на 5 сообщений
    ch <- "hello"
    fmt.Println(<-ch) // hello
}

Ключевые отличия

  • new(T) возвращает указатель *T; make(T) возвращает значение типа T.
  • new работает с любым типом; make — только со slice, map и channel.
  • new только обнуляет память; make инициализирует внутренние метаданные (длина, ёмкость, указатель на массив для slice; хэш-таблица для map; буфер для channel).
  • Карта, созданная через new(map[K]V), равна nil и запись в неё вызовет панику — нужен make.

Распространённые ошибки

// WRONG: nil map — паника при записи
p := new(map[string]int)
(*p)["key"] = 1 // panic: assignment to entry in nil map

// RIGHT:
m := make(map[string]int)
m["key"] = 1

// WRONG: slice через new — нет длины и ёмкости
sp := new([]int)
fmt.Println(len(*sp)) // 0, но нет capacity — append работает, но это awkward

// RIGHT:
sl := make([]int, 0, 10)
sl = append(sl, 1, 2, 3)

Когда использовать new()

На практике new() используется редко — чаще пишут &T{} для инициализации структуры и получения указателя. new() полезен, когда нужен указатель на встроенный тип (int, bool, string) без создания переменной:

// Pointer to bool — удобно для optional-полей в JSON/protobuf
func boolPtr(b bool) *bool { return &b } // ещё лаконичнее

// Или через new:
enabled := new(bool)
*enabled = true

Подводные камни

  • nil map при использовании new: new(map[K]V) создаёт указатель на nil-карту, запись в которую вызывает панику.
  • Инициализация канала через new: new(chan int) создаёт указатель на nil-канал; отправка в nil-канал блокируется вечно.
  • make не для структур: попытка вызвать make(MyStruct) — ошибка компиляции; для структур используйте new или литерал.
  • cap > len в make: при make([]int, 5, 10) элементы [5:10] существуют в памяти, но недоступны без append или re-slice — неявный источник багов.
  • Начальная ёмкость map: hint в make(map[K]V, n) — лишь подсказка, не гарантия; map всё равно растёт, но с меньшим числом rehash-операций.
  • Путаница new vs &T{}: оба эквивалентны для структур, но &T{field: val} позволяет инициализировать поля, а new(T) — нет.
  • Escape analysis: и new, и make могут выделять память на стеке (если компилятор доказал, что значение не уходит на heap) — не предполагайте, что это всегда heap-аллокация.

Common mistakes

  • Путать возвращаемые типы new и make.
  • Не понимать особую роль make для map/slice/channel.
  • Игнорировать nil map после new.

What the interviewer is testing

  • Может объяснить разницу на map или channel без заученной фразы.
  • Знает, что make возвращает готовое значение, а не указатель.
  • Понимает связь make с длиной и ёмкостью slice.

Sources

Related topics