Как стать автором
Обновить

Комментарии 13

Спасибо за статью! Кажется, вы забыли u = &User{} здесь:

Смотрите на два идентичных по сути выражения:

type User struct {    
  Name string    
  Age  int
}

u := new(User)

Вы разговариваете с нейросетью.

А что, для понимания GO надо изучать С? Знающие С сами разберутся с new и make, а не знающие ничего не поймут... Зачем эта статья? Только для рекламы Ваших мероприятий. Так для рекламы колонка спправа есть...

Казалось бы, всё и так понятно, но разложили по полочкам и структурировали. Спасибо!

Даже варианты использования new два раза написали

Можно это объяснить короче: 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: Цифровые навыки от ведущих экспертов

Ох уж эти эксперты, скажу я вам...

ссылочные структуры, к которым относятся только три типа: slicemap и 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[]bytechan 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, ошибки это, а не сигналы.

Позор, Отус, позорище!

Вы разговариваете с нейросетью.

Нейросеть захватила OTUS???

Судя по нескольким последним статьям, да.

Тут тот случай когда коммент важнее статьи. Огромное спасиб за критический разбор статьи. )

только вот тут не понял.

func SetDefaultPort(p *int) {
    if p == nil {
        p = new(int) // а если эту вот строчку убрать - ничего не сломается
      // но кое что изменится, угадайте, что
        *p = 8080
    }
    fmt.Println("Port:", *p)
}

если какую строчку убрать ничего не сломается? "p=new(int)" ?

ее убираю выходит паника.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий