В 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