Qual é o sentido de ter ponteiros no Go?

100

Eu sei que os ponteiros em Go permitem a mutação dos argumentos de uma função, mas não teria sido mais simples se eles adotassem apenas referências (com const apropriados ou qualificadores mutáveis). Agora temos ponteiros e, para alguns tipos internos, como mapas e canais, passam por referência implícitos.

Estou perdendo alguma coisa ou as dicas do Go são apenas uma complicação desnecessária?

anon
fonte
1
Aqui está uma questão que pode ajudar a esclarecer: stackoverflow.com/questions/795160/… Há uma diferença entre passar referências por valor e passar verdadeiramente por referência.
R. Martinho Fernandes
1
Nota: a questão é sobre Java, mas se aplica aqui também.
R. Martinho Fernandes
1
"e para alguns tipos integrados, como mapas e canais, passagem implícita por referência." Não, tudo é passado por valor no Go. Alguns tipos são (descritos informalmente como) tipos de referência, uma vez que possuem estado mutável interno.
newacct
O problema com esta questão é que "referências" não são uma coisa única com propriedades bem definidas. O termo "referências" é muito vago. Podemos ver nas respostas quantas pessoas lêem coisas diferentes na palavra "referências". Portanto, esta questão deve explicar exatamente quais diferenças existem entre os ponteiros Go e as referências que a questão tem em mente.
mtraceur

Respostas:

36

Eu realmente gosto de exemplos tirados de http://www.golang-book.com/8

func zero(x int) {
    x = 0
}
func main() {
    x := 5
    zero(x)
    fmt.Println(x) // x is still 5
}

em contraste com

func zero(xPtr *int) {
    *xPtr = 0
}
func main() {
    x := 5
    zero(&x)
    fmt.Println(x) // x is 0
}
Piotr Kochański
fonte
42
A questão era "por que temos ponteiros em vez de referências " e não entendo por que este exemplo não funcionaria com referências.
AndreKR
@AndreKR Porque podemos escolher se passamos por referência ou se passamos por valor. Existem alguns casos em que ambos podem ser desejáveis.
JDSweetBeat
9
@DJMethaneMan São "ponteiros x referências", não "ponteiros x passagem por valor"!
AndreKR
Como um comentário lateral, a passagem por referência foi adicionada no C # 2.0 por meio da palavra-chave "ref". Claro que os ponteiros são ainda mais convenientes em certos casos, porque podemos ter ponteiro a ponteiro ...
robbie fan
Eu não entendo como Go deve ser uma das linguagens populares mais fáceis e ainda assim eles contêm esse "recurso" ... É confuso e parece desnecessário, pelo menos para as pessoas que apontam isso aqui.
Akito
33

Os ponteiros são úteis por vários motivos. Os ponteiros permitem o controle sobre o layout da memória (afeta a eficiência do cache da CPU). Em Go podemos definir uma estrutura onde todos os membros estão em memória contígua:

type Point struct {
  x, y int
}

type LineSegment struct {
  source, destination Point
}

Nesse caso, as Pointestruturas são incorporadas à LineSegmentestrutura. Mas você nem sempre pode incorporar dados diretamente. Se você deseja oferecer suporte a estruturas como árvores binárias ou lista vinculada, você precisa oferecer suporte a algum tipo de ponteiro.

type TreeNode {
  value int
  left  *TreeNode
  right *TreeNode
}

Java, Python etc. não tem esse problema porque não permite que você insira tipos compostos, portanto, não há necessidade de diferenciar sintaticamente entre embutir e apontar.

Problemas com estruturas Swift / C # resolvidos com ponteiros Go

Uma alternativa possível para fazer o mesmo é diferenciar entre structe classcomo fazem C # e Swift. Mas isso tem limitações. Embora geralmente você possa especificar que uma função recebe uma estrutura como inoutparâmetro para evitar a cópia da estrutura, isso não permite que você armazene referências (ponteiros) para estruturas. Isso significa que você nunca pode tratar uma estrutura como um tipo de referência quando achar isso útil, por exemplo, para criar um alocador de pool (veja abaixo).

Alocador de memória personalizado

Usando ponteiros, você também pode criar seu próprio alocador de pool (isso é muito simplificado com muitas verificações removidas apenas para mostrar o princípio):

type TreeNode {
  value int
  left  *TreeNode
  right *TreeNode

  nextFreeNode *TreeNode; // For memory allocation
}

var pool [1024]TreeNode
var firstFreeNode *TreeNode = &pool[0] 

func poolAlloc() *TreeNode {
    node := firstFreeNode
    firstFreeNode  = firstFreeNode.nextFreeNode
    return node
}

func freeNode(node *TreeNode) {
    node.nextFreeNode = firstFreeNode
    firstFreeNode = node
}

Trocar dois valores

Ponteiros também permitem que você implemente swap. Isso é trocar os valores de duas variáveis:

func swap(a *int, b *int) {
   temp := *a
   *a = *b
   *b = temp
}

Conclusão

Java nunca foi capaz de substituir totalmente C ++ para programação de sistemas em locais como o Google, em parte porque o desempenho não pode ser ajustado para a mesma extensão devido à falta de capacidade de controlar o layout e o uso da memória (falhas de cache afetam o desempenho significativamente). Go tem como objetivo substituir C ++ em muitas áreas e, portanto, precisa oferecer suporte a ponteiros.

Erik Engheim
fonte
7
C # permite passar estruturas por referência. Veja as palavras-chave "ref" e "out".
olegz
1
Ok, então é como o Swift. Vou pensar em uma maneira de atualizar meu exemplo.
Erik Engheim
29

As referências não podem ser reatribuídas, enquanto os ponteiros podem. Isso por si só torna os ponteiros úteis em muitas situações em que as referências não podem ser usadas.

zildjohn01
fonte
17
Se as referências são reatribuíveis, é um problema de implementação específico da linguagem.
crantok
28

Go foi projetado para ser uma linguagem concisa e minimalista. Portanto, começou apenas com valores e ponteiros. Posteriormente, por necessidade, alguns tipos de referência (fatias, mapas e canais) foram adicionados.


A linguagem de programação Go: Perguntas frequentes sobre design de linguagem: Por que mapas, fatias e canais são referências enquanto matrizes são valores?

"Há muita história sobre esse assunto. No início, mapas e canais eram ponteiros sintaticamente e era impossível declarar ou usar uma instância sem ponteiro. Além disso, lutamos para saber como os arrays deveriam funcionar. Eventualmente, decidimos que a separação estrita de ponteiros e valores tornou a linguagem mais difícil de usar. A introdução de tipos de referência, incluindo fatias para lidar com a forma de referência dos arrays, resolveu esses problemas. Os tipos de referência adicionam alguma complexidade lamentável à linguagem, mas têm um grande efeito na usabilidade: Go tornou-se um linguagem mais produtiva e confortável quando foram apresentados. "


A compilação rápida é um dos principais objetivos do design da linguagem de programação Go; isso tem seus custos. Uma das baixas parece ser a capacidade de marcar variáveis ​​(exceto para constantes de tempo de compilação básicas) e parâmetros como imutáveis. Foi solicitado, mas recusado.


nozes de golang: vá a linguagem. Algum feedback e dúvidas.

"Adicionar const ao sistema de tipo força-o a aparecer em todos os lugares e força a removê-lo de todos os lugares se algo mudar. Embora possa haver algum benefício em marcar objetos imutáveis ​​de alguma forma, não achamos que um qualificador de tipo const seja adequado ir."

PeterSO
fonte
FWIW, "tipos de referência" no Go também podem ser reatribuídos. Eles são mais como ponteiros implícitos?
Matt Joiner
1
Eles são apenas sintaxe especial para estruturas que contêm um ponteiro (e comprimento, capacidade, ...).
mk12 de