Respostas:
Coisas que você pode fazer e make
que não pode fazer de outra maneira:
É 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
}
m := map[string]int{}
vez de m := make(map[string]int)
? não é necessário pré-alocar o tamanho também.
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.
new
pode 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 new
e make
pode 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 new
e 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 new
e make
em 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 new
e make
permanecer separado.
int
é criado.
make(Point)
e make(int)
nessas duas últimas linhas?
make
A 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
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.
new(T)
- Aloca memória e define o valor zero para o tipo T ... que
é 0
para int , ""
para string e nil
para 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
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.
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 make
com new
faz pouco sentido, pois os dois desempenham funções totalmente diferentes. Mas isso é explicado em detalhes no artigo vinculado.
&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)
.