Por que eu faria () ou novo ()?

203

Os documentos de introdução dedicam muitos parágrafos para explicar a diferença entre new()e make(), mas na prática, você pode criar objetos no escopo local e devolvê-los.

Por que você usaria o par de alocadores?

slezica
fonte

Respostas:

170

Coisas que você pode fazer e makeque não pode fazer de outra maneira:

  • Crie um canal
  • Criar um mapa com espaço pré-alocado
  • Crie uma fatia com espaço pré-alocado ou com len! = Cap

É um pouco mais difícil de justificar new. A principal coisa que facilita é criar ponteiros para tipos não compostos. As duas funções abaixo são equivalentes. Um é apenas um pouco mais conciso:

func newInt1() *int { return new(int) }

func newInt2() *int {
    var i int
    return &i
}
Evan Shaw
fonte
41
É verdade que 'novo' não pode ser usado para criar canais. No entanto, na minha opinião, o ponto é: o que aconteceria se 'new' e 'make' fossem unidos em uma única função interna? Certamente, essa substituição seria possível. Como é possível, a pergunta é: quais são as razões objetivas para ter 2 funções internas em vez de apenas 1 função interna generalizada. - Sua resposta diz corretamente que 'novo' não pode ser usado para criar canais / mapas / fatias, mas não fornece justificativas para o motivo pelo qual o Go tem 'novo' e 'make', em vez de ter uma função generalizada init + init.
5
Eles poderiam ser combinados e até foi proposto por Rob Pike em um ponto: groups.google.com/d/topic/golang-nuts/kWXYU95XN04/discussion . Por fim, ele não passou por motivos semelhantes aos dados em sua resposta.
Evan Shaw
12
A ação efetiva mostra que new retorna um valor zerado, enquanto o mapa aloca tipos de mapas não zerados, fatia ou canal. Veja golang.org/doc/effective_go.html#allocation_new
kristianp
E em m := map[string]int{}vez de m := make(map[string]int)? não é necessário pré-alocar o tamanho também.
Noam Manos
165

O Go possui várias maneiras de alocação de memória e inicialização de valor:

&T{...}, &someLocalVar, new,make

A alocação também pode ocorrer ao criar literais compostos.


newpode ser usado para alocar valores como números inteiros, &inté ilegal:

new(Point)
&Point{}      // OK
&Point{2, 3}  // Combines allocation and initialization

new(int)
&int          // Illegal

// Works, but it is less convenient to write than new(int)
var i int
&i

A diferença entre newe makepode ser vista observando o seguinte exemplo:

p := new(chan int)   // p has type: *chan int
c := make(chan int)  // c has type: chan int

Suponha que o Go não tenha newe make, mas tenha a função interna NEW. Em seguida, o código de exemplo ficaria assim:

p := NEW(*chan int)  // * is mandatory
c := NEW(chan int)

O * seria obrigatório , então:

new(int)        -->  NEW(*int)
new(Point)      -->  NEW(*Point)
new(chan int)   -->  NEW(*chan int)
make([]int, 10) -->  NEW([]int, 10)

new(Point)  // Illegal
new(int)    // Illegal

Sim, fundindo newe makeem uma única função built-in é possível. No entanto, é provável que uma única função interna leve a mais confusão entre os novos programadores Go do que ter duas funções internas.

Considerando todos os pontos acima, parece mais apropriado newe makepermanecer separado.


fonte
@ TorstenBronger Acho que o novo é mais fácil de ler e mostra que essa é a instância em que o inté criado.
Daniel Toebe
4
Você quis escrever make(Point)e make(int)nessas duas últimas linhas?
Jimmy Huch
27

makeA função aloca e inicializa um objeto do tipo fatia, mapa ou chan apenas. Como new, o primeiro argumento é um tipo. Mas, também pode levar um segundo argumento, o tamanho. Diferente do novo, o tipo de retorno do make é o mesmo que o tipo de argumento, não um ponteiro para ele. E o valor alocado é inicializado (não definido como valor zero como no novo). A razão é que fatia, mapa e chan são estruturas de dados. Eles precisam ser inicializados, caso contrário, não serão utilizáveis. Esta é a razão pela qual new () e make () precisam ser diferentes.

Os exemplos a seguir do Effective Go deixam muito claro:

p *[]int = new([]int) // *p = nil, which makes p useless
v []int = make([]int, 100) // creates v structure that has pointer to an array, length field, and capacity field. So, v is immediately usable
Sinatra
fonte
1
Em new([]int), ele apenas aloca memória para [] int, mas não inicializa, apenas retorna nil; não o ponteiro para a memória porque é inutilizável. make([]int)aloca e inicializa para que seja utilizável; em seguida, retorne seu endereço.
precisa saber é o seguinte
12
  • new(T)- Aloca memória e define o valor zero para o tipo T ... que
    é 0para int , ""para string e nilpara tipos referenciados ( fatia , mapa , chan )

    Observe que os tipos referenciados são apenas ponteiros para algumas estruturas de dados subjacentes , que não serão criadas pelo new(T)
    Exemplo: no caso de fatia , a matriz subjacente não será criada, new([]int) retornando assim um ponteiro a nada

  • make(T)- Aloca memória para tipos de dados referenciados ( fatia , mapa , chan ), além de inicializar suas estruturas de dados subjacentes

    Exemplo: no caso de fatia , a matriz subjacente será criada com o comprimento e a capacidade especificados.
    Lembre-se de que, diferentemente de C, uma matriz é um tipo primitivo no Go!


Dito isto:

  • make(T) comporta-se como sintaxe literal composta
  • new(T)se comporta como var(quando a variável não é inicializada)

    func main() {
        fmt.Println("-- MAKE --")
        a := make([]int, 0)
        aPtr := &a
        fmt.Println("pointer == nil :", *aPtr == nil)
        fmt.Printf("pointer value: %p\n\n", *aPtr)
    
        fmt.Println("-- COMPOSITE LITERAL --")
        b := []int{}
        bPtr := &b
        fmt.Println("pointer == nil :", *bPtr == nil)
        fmt.Printf("pointer value: %p\n\n", *bPtr)
    
        fmt.Println("-- NEW --")
        cPtr := new([]int)
        fmt.Println("pointer == nil :", *cPtr == nil)
        fmt.Printf("pointer value: %p\n\n", *cPtr)
    
        fmt.Println("-- VAR (not initialized) --")
        var d []int
        dPtr := &d
        fmt.Println("pointer == nil :", *dPtr == nil)
        fmt.Printf("pointer value: %p\n", *dPtr)
    }

    Execute o programa

    -- MAKE --
    pointer == nil : false
    pointer value: 0x118eff0  # address to underlying array
    
    -- COMPOSITE LITERAL --
    pointer == nil : false
    pointer value: 0x118eff0  # address to underlying array
    
    -- NEW --
    pointer == nil : true
    pointer value: 0x0
    
    -- VAR (not initialized) --
    pointer == nil : true
    pointer value: 0x0

    Leitura adicional:
    https://golang.org/doc/effective_go.html#allocation_new https://golang.org/doc/effective_go.html#allocation_make

  • Loris
    fonte
    as coisas ficam mais claras com um exemplo. voto positivo :)
    Sumit Jha
    8

    Você precisa make()criar canais e mapas (e fatias, mas eles também podem ser criados a partir de matrizes). Não há maneira alternativa de fazer isso, então você não pode remover make()do seu léxico.

    Quanto a new(), não conheço nenhuma razão imediata por que você precisa quando pode usar a sintaxe struct. Porém, ele possui um significado semântico exclusivo, que é "criar e retornar uma estrutura com todos os campos inicializados com seu valor zero", o que pode ser útil.

    Lily Ballard
    fonte
    1
    Portanto, novos devem ser evitados e basta preferir o uso da sintaxe Struct
    CommonSenseCode
    8

    Além de tudo explicado no Effective Go , a principal diferença entre new(T)e &T{}é que o último executa explicitamente uma alocação de heap. No entanto, deve-se notar que isso depende da implementação e, portanto, pode estar sujeito a alterações.

    Comparando makecom newfaz pouco sentido, pois os dois desempenham funções totalmente diferentes. Mas isso é explicado em detalhes no artigo vinculado.

    jimt
    fonte
    10
    A reivindicação que &T{}executa explicitamente uma alocação de heap é AFAIK, não baseada em nada nas especificações. Na verdade, acredito que a análise de escape já esteja mantendo esse * T na pilha sempre que possível, exatamente da mesma maneira que comnew(T) .
    Zzzz
    6

    novo (T): retorna um ponteiro para digitar T um valor do tipo * T, aloca e zera a memória. new (T) é equivalente a & T {} .

    make (T): retorna um valor inicializado do tipo T , aloca e inicializa a memória. É usado para fatias, mapa e canais.

    M.Nair
    fonte