No Go, existem várias maneiras de retornar um struct
valor ou fatia dele. Para os individuais que eu já vi:
type MyStruct struct {
Val int
}
func myfunc() MyStruct {
return MyStruct{Val: 1}
}
func myfunc() *MyStruct {
return &MyStruct{}
}
func myfunc(s *MyStruct) {
s.Val = 1
}
Eu entendo as diferenças entre estes. O primeiro retorna uma cópia da estrutura, o segundo um ponteiro para o valor da estrutura criado na função, o terceiro espera que uma estrutura existente seja passada e substitui o valor.
Já vi todos esses padrões serem usados em vários contextos, estou me perguntando quais são as melhores práticas em relação a eles. Quando você usaria qual? Por exemplo, o primeiro pode ser aprovado para estruturas pequenas (porque a sobrecarga é mínima), o segundo para estruturas maiores. E a terceira, se você deseja ser extremamente eficiente em termos de memória, porque é possível reutilizar facilmente uma única instância de estrutura entre as chamadas. Existem práticas recomendadas para quando usar quais?
Da mesma forma, a mesma pergunta sobre fatias:
func myfunc() []MyStruct {
return []MyStruct{ MyStruct{Val: 1} }
}
func myfunc() []*MyStruct {
return []MyStruct{ &MyStruct{Val: 1} }
}
func myfunc(s *[]MyStruct) {
*s = []MyStruct{ MyStruct{Val: 1} }
}
func myfunc(s *[]*MyStruct) {
*s = []MyStruct{ &MyStruct{Val: 1} }
}
Novamente: quais são as melhores práticas aqui. Sei que as fatias são sempre ponteiros, portanto, não é útil retornar um ponteiro para uma fatia. No entanto, devo retornar uma fatia dos valores de struct, uma fatia de ponteiros para estruturas, devo passar um ponteiro para uma fatia como argumento (um padrão usado na API do Go App Engine )?
new(MyStruct)
:) Mas não há realmente nenhuma diferença entre os diferentes métodos de alocação de ponteiros e retorná-los.Respostas:
tl; dr :
Um caso em que você costuma usar um ponteiro:
Algumas situações em que você não precisa de ponteiros:
As diretrizes de revisão de código sugerem a passagem de pequenas estruturas como
type Point struct { latitude, longitude float64 }
, e talvez até um pouco maiores, como valores, a menos que a função que você está chamando precise modificá-las.bytes.Replace
necessários dez palavras 'args (três fatias e umint
).Para fatias , você não precisa passar um ponteiro para alterar os elementos da matriz.
io.Reader.Read(p []byte)
altera os bytes dep
, por exemplo. É sem dúvida um caso especial de "tratar pequenas estruturas como valores", já que internamente você está passando por uma pequena estrutura chamada cabeçalho de fatia (consulte a explicação de Russ Cox (rsc) ). Da mesma forma, você não precisa de um ponteiro para modificar um mapa ou se comunicar em um canal .Para fatias, você redimensionará (altere o início / comprimento / capacidade de), funções internas como
append
aceitar um valor de fatia e retornar um novo. Eu imitaria isso; evita alias, retornar uma nova fatia ajuda a chamar a atenção para o fato de que uma nova matriz pode ser alocada e é familiar para os chamadores.interface{}
parâmetro.Mapas, canais, seqüências de caracteres e valores de função e interface , como fatias, são referências ou estruturas internas que já contêm referências; portanto, se você está apenas tentando evitar a cópia dos dados subjacentes, não precisa passar ponteiros para eles . (rsc escreveu um post separado sobre como os valores da interface são armazenados ).
flag.StringVar
leva a*string
por esse motivo, por exemplo.Onde você usa ponteiros:
Considere se sua função deve ser um método em qualquer estrutura para a qual você precise de um ponteiro. As pessoas esperam que muitos métodos
x
sejam modificadosx
, portanto, tornar a estrutura modificada o receptor pode ajudar a minimizar a surpresa. Existem diretrizes sobre quando os receptores devem ser indicadores.As funções que têm efeitos em seus parâmetros não receptores devem deixar isso claro no godoc, ou melhor ainda, no godoc e no nome (como
reader.WriteTo(writer)
).Você menciona aceitar um ponteiro para evitar alocações, permitindo a reutilização; alterar as APIs para reutilizar a memória é uma otimização que eu atrasaria até ficar claro que as alocações têm um custo não trivial e, em seguida, procuraria uma maneira que não force a API mais complicada para todos os usuários:
bytes.Buffer
.Reset()
método para colocar um objeto novamente em um estado em branco, como alguns tipos de stdlib oferecem. Os usuários que não se importam ou não podem salvar uma alocação não precisam chamá-la.existingUser.LoadFromJSON(json []byte) error
podem ser envolvidosNewUserFromJSON(json []byte) (*User, error)
. Mais uma vez, ele opta pela escolha entre preguiça e alocação de beliscões para o chamador individual.sync.Pool
lidar com alguns detalhes. Se uma alocação específica cria muita pressão de memória, você tem certeza de que sabe quando a alocação não é mais usada e não possui uma otimização melhor disponível,sync.Pool
pode ajudar. (A CloudFlare publicou umasync.Pool
publicação útil (pré- ) no blog sobre reciclagem.)Por fim, se suas fatias devem ter ponteiros: fatias de valores podem ser úteis e poupar alocações e erros de cache. Pode haver bloqueadores:
NewFoo() *Foo
vez de deixar Go inicializar com o valor zero .append
copia itens quando aumenta a matriz subjacente . Os indicadores que você obteve antes doappend
ponto para o lugar errado depois, a cópia pode ser mais lenta para estruturas enormes e, por exemplo, async.Mutex
cópia não é permitida. Insira / exclua no meio e classifique da mesma forma os itens.Em termos gerais, as fatias de valor podem fazer sentido se você colocar todos os seus itens no lugar com antecedência e não os mover (por exemplo, não mais
append
s após a configuração inicial) ou se você continuar movendo-os, mas você tem certeza de que é OK (não / uso cuidadoso de ponteiros para itens, os itens são pequenos o suficiente para copiar com eficiência, etc.). Às vezes você precisa pensar ou medir as especificidades da sua situação, mas esse é um guia aproximado.fonte
Replace(s, old, new []byte, n int) []byte
; s, old e new são três palavras cada ( cabeçalhos de fatia são(ptr, len, cap)
) en int
são uma palavra; portanto, 10 palavras, que em oito bytes / palavra são 80 bytes.Três razões principais para você querer usar receptores de método como ponteiros:
"Primeiro, e mais importante, o método precisa modificar o receptor? Se o fizer, o receptor deve ser um ponteiro."
"A segunda é a consideração da eficiência. Se o receptor é grande, uma grande estrutura, por exemplo, será muito mais barato usar um receptor de ponteiro".
"Em seguida, vem a consistência. Se alguns dos métodos do tipo precisam ter receptores de ponteiro, o restante também deve, portanto, o conjunto de métodos é consistente, independentemente de como o tipo é usado"
Referência: https://golang.org/doc/faq#methods_on_values_or_pointers
Editar: Outra coisa importante é saber o "tipo" real que você está enviando para funcionar. O tipo pode ser um 'tipo de valor' ou 'tipo de referência'.
Mesmo que as fatias e os mapas funcionem como referências, podemos passá-los como ponteiros em cenários como alterar o comprimento da fatia na função.
fonte
Um caso em que você geralmente precisa retornar um ponteiro é ao construir uma instância de algum recurso com estado ou compartilhável . Isso geralmente é feito por funções prefixadas com
New
.Como eles representam uma instância específica de algo e podem precisar coordenar alguma atividade, não faz muito sentido gerar estruturas duplicadas / copiadas representando o mesmo recurso - portanto, o ponteiro retornado atua como o identificador do próprio recurso .
Alguns exemplos:
func NewTLSServer(handler http.Handler) *Server
- instanciar um servidor web para testefunc Open(name string) (*File, error)
- retornar um identificador de acesso a arquivosEm outros casos, os ponteiros são retornados apenas porque a estrutura pode ser muito grande para copiar por padrão:
func NewRGBA(r Rectangle) *RGBA
- alocar uma imagem na memóriaComo alternativa, o retorno de ponteiros diretamente pode ser evitado retornando uma cópia de uma estrutura que contém o ponteiro internamente, mas talvez isso não seja considerado idiomático:
fonte
Se você puder (por exemplo, um recurso não compartilhado que não precise ser passado como referência), use um valor. Pelas seguintes razões:
Razão 1 : você alocará menos itens na pilha. Alocar / desalocar da pilha é imediato, mas alocar / desalocar no Heap pode ser muito caro (tempo de alocação + coleta de lixo). Você pode ver alguns números básicos aqui: http://www.macias.info/entry/201802102230_go_values_vs_references.md
Razão 2 : especialmente se você armazenar valores retornados em fatias, seus objetos de memória serão mais compactados na memória: repetir uma fatia em que todos os itens são contíguos é muito mais rápido do que repetir uma fatia em que todos os itens são ponteiros para outras partes da memória . Não para a etapa de indireção, mas para o aumento de erros de cache.
Disjuntor de mito : uma linha de cache x86 típica tem 64 bytes. A maioria das estruturas é menor que isso. O tempo de copiar uma linha de cache na memória é semelhante a copiar um ponteiro.
Somente se uma parte crítica do seu código for lenta, eu tentaria alguma micro-otimização e verificaria se o uso de ponteiros melhora um pouco a velocidade, com o custo de menos legibilidade e manutenção.
fonte