В Go имеется две важные функции для выделения памяти: new и make. На первый взгляд похожие, они используются для разных целей и типов. Расскажем о функции new.
Функция new
Функцией new(T) выделяется память для нового элемента типа T и возвращается указатель на эту выделенную память, т. е. значение типа *T.
На Go, в отличие от других языков, этой
newне инициализируется выделенная память, а просто обнуляется пространство.
Почему это важно?
При проектировании структур данных рекомендуется убедиться, что нулевое значение каждого типа полезно, не прибегая к дополнительной инициализации. Так, применив new, разработчик немедленно приступает к возвращаемому значению.
Типичный пример — пакет bytes. В документации четко обозначено, что «нулевое значение Buffer — это пустой буфер, готовый к использованию». Аналогично в типах вроде sync.Mutex нет явных конструкторов, поскольку нулевое значение Mutex — это готовый к использованию разблокированный мьютекс.
Пример 1. SyncedBuffer
Применим этот принцип к пользовательской структуре:
type SyncedBuffer struct {
lock sync.Mutex
buffer bytes.Buffer
}
p := new(SyncedBuffer) // *SyncedBuffer
var v SyncedBuffer // SyncedBuffer
В этом случае и p, и v готовы к использованию без дополнительной инициализации: сразу же блокировать, разблокировать ими буфер или выполнять над ним действия. Комбинация типов sync.Mutex и bytes.Buffer транзитивна, то есть ими наследуется свойство полезности нулевого значения.
Пример 2. Работа со срезами
Вот пользовательская структура среза — еще один пример, где благодаря new упрощается инициализация:
type MySlice struct {
data []int
}
p := new(MySlice) // *MySlice
p.data = append(p.data, 1, 2, 3)
fmt.Println(p.data) // Вывод: [1 2 3]
В этом примере p — это указатель на структуру MySlice с вновь выделяемой памятью. Хотя поле среза data изначально пустое, элементы к нему добавляются напрямую, поскольку нулевое значение среза — nil. Когда же с пустыми срезами встречается append, память выделяется автоматически.
Пример 3. Карты
При помощи new выделяется память для большинства типов, но эту функцию нельзя использовать с картами: для инициализации им требуется make. Тем не менее new применяется для структур, в которых содержатся карты:
type MyMap struct {
m map[string]int
}
p := new(MyMap) // *MyMap
p.m = make(map[string]int)
p.m["one"] = 1
fmt.Println(p.m) // Вывод: map[one:1]
Здесь p — это указатель на MyMap, но нулевое значение карты равно nil, поэтому перед использованием ее необходимо явно инициализировать с помощью make. Это важное различие двух функций выделения памяти.
Главные выводы
- Функцией
new(T)для любого типаTвыделяется «обнуленная» память и возвращается указатель типа*T. - Важно проектировать типы такими, чтобы их нулевые значения были полезными без дальнейшей инициализации.
newприменяется со структурами, массивами, срезами и указателями,make— для инициализации срезов, карт и каналов.
Благодаря пониманию того, как и когда использовать new, на Go пишется более чистый и эффективный код, сокращаются стереотипный код и риск возникновения багов.
Читайте также:
- Как использовать перечисления в Golang
- Синхронизация данных в реальном времени между MongoDB и Elasticsearch на Golang
- Пишем балансировщик нагрузки на Golang
Читайте нас в Telegram, VK и Дзен
Перевод статьи Lenon Rodrigues: Go Tip #1: Memory Allocation with new





