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.