Зачем в Go нужны функции make() и new()? В чём разница?
Всем привет! Я начал изучать Go недавно и никак не могу понять, зачем нужны две отдельные функции для выделения памяти: make() и new(). Вроде обе создают объекты, но примеры из документации меня только запутали.
Вот что я пытаюсь сделать. Я хочу создать срез (slice) и сразу добавить в него элементы:
package main
import "fmt"
func main() {
// Пытаюсь создать срез через new
slice1 := new([]int)
fmt.Println(slice1) // Выводит: &[]
// Но как добавить элемент? slice1 = append(slice1, 1) не работает
// Ошибка: first argument to append must be slice; have *[]int
// Пытаюсь создать срез через make
slice2 := make([]int, 5)
fmt.Println(slice2) // Выводит: [0 0 0 0 0]
// Работает, но почему длина сразу 5? Можно ли без длины?
// А с мапой та же история
var m1 map[string]int
// m1["key"] = 1 // panic: assignment to entry in nil map
m2 := make(map[string]int)
m2["key"] = 1 // Всё ок
}
Почему new() возвращает указатель, а make() — нет? Зачем вообще нужен new(), если make() сразу делает рабочие срезы и мапы? И почему для обычных структур, например type Person struct { Name string }, используют &Person{}, а не make()?
Объясните, пожалуйста, простыми словами для новичка.
<<Главное различие: make() для ссылочных типов, new() — для всего остального
В Go два механизма выделения памяти для разных целей. Запомните простое правило:
- make() — только для срезов (slice), мап (map) и каналов (channel). Возвращает значение (не указатель). Инициализирует внутреннюю структуру.
- new() — для любых типов, включая структуры и базовые типы. Возвращает указатель на нулевое значение.
Почему make() не возвращает указатель?
Срезы, мапы и каналы в Go — это ссылочные типы. Они уже содержат указатель на внутренние данные. make() выделяет память для этих внутренних структур и возвращает готовый к использованию объект. Если бы make() вернул указатель, получился бы «указатель на указатель» — это избыточно.
Пример с срезом:
// new() — бесполезно для срезов
s1 := new([]int)
*s1 = append(*s1, 1, 2, 3) // Можно, но неудобно
fmt.Println(*s1) // [1 2 3]
// make() — правильно
s2 := make([]int, 0, 3) // длина 0, ёмкость 3
s2 = append(s2, 1, 2, 3)
fmt.Println(s2) // [1 2 3]
Обратите внимание: make() требует указать длину (для среза) или ёмкость (опционально). Если не знаете размер, используйте make([]int, 0) — это создаст пустой срез, готовый к append.
Когда использовать new()?
new() полезен для структур и базовых типов, когда вам нужен указатель:
type Person struct {
Name string
Age int
}
// Способ 1: new()
p1 := new(Person)
p1.Name = "Alice"
p1.Age = 30
fmt.Println(p1) // &{Alice 30}
// Способ 2: &Person{} — более идиоматично
p2 := &Person{Name: "Bob", Age: 25}
fmt.Println(p2) // &{Bob 25}
// Для базовых типов
num := new(int)
*num = 42
fmt.Println(*num) // 42
Разницы между new(T) и &T{} нет — оба возвращают *T. Но &T{} позволяет сразу задать поля, поэтому его используют чаще.
Почему var m map[string]int не работает?
Объявление var m map[string]int создаёт nil-мапу. В неё нельзя писать — будет паника. make() инициализирует внутреннюю хеш-таблицу:
// Неправильно
var m1 map[string]int
// m1["key"] = 1 // panic
// Правильно
m2 := make(map[string]int)
m2["key"] = 1 // OK
// Также можно создать пустую мапу литералом
m3 := map[string]int{}
m3["key"] = 1 // OK
Резюме
| Функция | Для каких типов | Возвращает | Инициализация |
|---|---|---|---|
make() | slice, map, channel | Значение (не указатель) | Выделяет внутренние структуры |
new() | Любые типы | Указатель (*T) | Обнуляет память |
Практический совет: Используйте make() для срезов, мап и каналов. Для структур — &Struct{}. new() оставьте для редких случаев, когда нужно получить указатель на базовый тип или когда вы пишете обобщённый код с дженериками.
Дополнение: new() может быть полезен в дженериках и рефлексии
Хотя в повседневном коде new() используется редко, есть сценарии, где без него не обойтись:
- Дженерики (Go 1.18+): Если вы пишете обобщённую функцию, которая должна создать экземпляр типа T,
new()— единственный способ:
func Zero[T any]() *T {
return new(T) // Работает для любого T
}
func main() {
p := Zero[Person]()
fmt.Println(p) // &{ 0}
}
- Рефлексия (reflect): При работе с типами через reflect.New() вы получаете указатель, аналогичный
new(). - Производительность: В некоторых бенчмарках
new()может быть чуть быстрее&T{}для простых структур, но разница обычно незначительна.
Также стоит помнить: make() не работает с типами, определёнными пользователем. Если у вас type MySlice []int, создавать его нужно через make(MySlice, 0), а не через new(MySlice).
Итог: Начинающим можно вообще забыть про new() и использовать make() для трёх типов, а для всего остального — &Type{}. Но знать о new() полезно для глубокого понимания языка.
Ответов пока нет.
Войдите чтобы оставить ответ