Комментарии 13
Спасибо за статью! Кажется, вы забыли u = &User{} здесь:
Смотрите на два идентичных по сути выражения:
type User struct {
Name string
Age int
}
u := new(User)
А что, для понимания GO надо изучать С? Знающие С сами разберутся с new и make, а не знающие ничего не поймут... Зачем эта статья? Только для рекламы Ваших мероприятий. Так для рекламы колонка спправа есть...
Казалось бы, всё и так понятно, но разложили по полочкам и структурировали. Спасибо!
Можно это объяснить короче: make - запускает конструкторы внутренних структур, new - нет
А
new(User)
всегда аллоцирует объект в куче
Могу ошибаться, но allocation зависит исключительно от escape analysis, а не от синтаксиса. Отличие &T{} от new лишь в том, что можно тут же забить значениями.
Пробуем
type T struct {
X int
Y bool
}
func main() {
p1 := new(T)
p2 := &T{X: 1, Y: true}
= p1
= p2
}
Затем
go run -gcflags="-m" main.go
и получаем:
./main.go:9:11: new(T) does not escape
./main.go:10:8: &T{...} does not escape
В обоих случаях выделяются в стеке.
OTUS: Цифровые навыки от ведущих экспертов
Ох уж эти эксперты, скажу я вам...
ссылочные структуры, к которым относятся только три типа:
slice
,map
иchan
Давайте попробуем понять, что такое "ссылочная структура". Ссылочный тип, вы имели в виду?
Вот есть мапа - она ссылочная (только не структура, она буквально алиас для указателя на hMap). А слайс с каналом тут каким боком?
и просто так через «var» их не завести — получим nil и панику при первом же обращении
Зачем вы неправду-то рассказываете?
var m map[int]int
fmt.Println(m[0]) // 0, нет паники
var ch chan int
<- ch // все еще нет паники (есть fatal error: deadlock, но только если рядом нет другой горутины)
Но есть разница:
&User{}
создаёт сразу полностью известную структуру
new(User) создает ровно такую же.
1. В generic‑коде, где
T
неизвестен на этапе компиляции:
func NewPointer[T any]() *T {return new(T)}
Это невозможно выразить через
&T{}
, потому чтоT
может быть чем угодно:int
,[]byte
,chan string
, и у вас просто нет доступа к литералу.
func NewPointer[T any]() *T {
var t T
return &t
}
А вот так, внезапно, можно
Когда вы хотите получить нулевой объект, но не хотите указывать поля, и вам неважно, что они все обнулены:
conn := new(net.Conn)
А почему не вот так?
conn := &net.Conn{}
var cn net.Conn
conn = &cn
В структурах, где не нужны начальные значения, например, когда вы вручную наполняете поля позже:
type Builder struct {
parts []string
}
b := new(Builder)
b.parts = append(b.parts, "step1", "step2")
То же самое, но не обязательно целиком в куче:
b := &Builder{}
b.parts = append(b.parts, "step1", "step2")
Можно ли делать new([]int)? А new(map[string]string)?
Вместо make? Нельзя. Ну, просто потому, что new([]int) вернет указатель на слайс, а make([]int) - собственно слайс.
Можно вот так: `slice := *new([]int)` - подумайте, для чего тут "звездочка"
В этом и есть подвох:
new
вернёт указатель на пустой контейнер. Это будетnil
, и при попытке использовать его как полноценныйslice
илиmap
вы быстро огребёте:
m := new(map[string]int)
(*m)["foo"] = 42 // panic: assignment to entry in nil map
Ну вот, вырожденный пример с мапой. Мапа паникует не из-за того, что ее через new создавали, а из-за того, что в нее явно запрещена запись до инициализации. `fmt.Println((*m)[1])` - вот эта штука не паникует, нормальная мапа.
А давайте со слайсом провернем то же самое?
slice := *new([]int)
slice = append(slice, 2)
fmt.Println(slice)
И ничего не случилось, никаких паник, все законно.
А вот тут:
func SetDefaultPort(p *int) {
if p == nil {
p = new(int) // а если эту вот строчку убрать - ничего не сломается
// но кое что изменится, угадайте, что
*p = 8080
}
fmt.Println("Port:", *p)
}
Или для паттерна «опциональное значение через nil»:
type Options struct {
RetryCount *int
}
А здесь вам new зачем нужен?
&Options{} и new(Options) в поле RetryCount будут содержать совершенно идентичные nil'ы...
Да и вообще паттерн Options не так реализовывается.
make
в Go — это не про выделение памяти какnew
. Это про инициализацию ссылочных структур, которые не могут существовать без подготовки
Вполне существуют, просто надо понимать, что с ними неинициализированными делать можно...
Если вы создаёте слайс через
make
, то Go делает примерно следующее:
// Псевдокод, близкий к реальности:
array := malloc(sizeof(T) * cap)
header := sliceHeader{ Data: &array[0], Len: len, Cap: cap,}
return header
Достаточно далеко от реальности, например. Потому что делает он следующее:
make([]int, 50, 100)
new([100]int)[0:50]
Вот тут можно ознакомиться: https://go.dev/ref/spec#Slice_types
Вот почему
make([]int, 0, 100)
— это рабочий, но нулевой по длине слайс. Его можноappend
'ить без аллокаций до 100 элементов. Если бы вы написалиvar s []int
, вы бы получилиnil
‑слайс, у которого иptr
, иlen
, иcap
— нули.
make([]int, 0, 100) - это 24 байта заголовка + new([100]int) - непрерывный кусок памяти в хипе.
Попробуйте взять у него
s[0]
— получите панику.make
защищает от этого, создавая слайс, который реально готов к работе.
Ну вы хоть проверяйте то, что пишете:
sliceByNew := new([]int)
fmt.Println(sliceByNew[0]) // верно, паника
sliceByMake := make([]int, 0, 100)
fmt.Println(sliceByMake[0]) // приколись, тоже паника!!!
Когда ты читаешь
make(chan error, 1)
, ты сразу видишь: «канал односторонний, используется для отправки единственного сигнала».
Это где я это должен сразу увидеть???
Откуда "односторонний" канал-то??? `<-chan Type`, `chan<- Type` - односторонние, а вот это - обычный канал.
А откуда "единственный сигнал"? 1 - это размер буфера! Я могу миллион "сигналов" этих ваших напосылать, при наличии читателя. Причем даже без буфера вообще - тоже могу.
Да и откуда "сигналы"-то взялись? Явно же написано chan error, ошибки это, а не сигналы.
Позор, Отус, позорище!
Вы разговариваете с нейросетью.
Тут тот случай когда коммент важнее статьи. Огромное спасиб за критический разбор статьи. )
Golang: когда make, когда new