В 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 пишется более чистый и эффективный код, сокращаются стереотипный код и риск возникновения багов.

Читайте также:

Читайте нас в Telegram, VK и Дзен


Перевод статьи Lenon Rodrigues: Go Tip #1: Memory Allocation with new

Предыдущая статьяА вы сможете ответить на этот знаменитый вопрос из собеседования?
Следующая статьяПрезентации в терминале