Embedding em um exemplo
Um campo com tipo mas sem nome é um campo embutido. Os campos e métodos do tipo embutido passam a ser acessíveis diretamente no tipo externo.
Saída:
Ana
Hi, I'm Ana
ana@example.com
{User:{Name:Ana Email:ana@example.com} Level:2}
O nome do campo embutido é o nome do seu tipo, User. É assim que você se refere a ele em literais (User: User{...}) e quando precisa do próprio valor interno (a.User). Um campo promovido não pode ser definido pelo nome curto em um literal: Admin{Name: "Ana"} falha com unknown field Name in struct literal of type Admin.
Métodos promovidos satisfazem interfaces
A promoção acrescenta os métodos do tipo embutido ao conjunto de métodos do tipo externo. Então o tipo externo satisfaz todas as interfaces que o tipo interno satisfaz.
As regras de conjunto de métodos seguem o tipo embutido. Embutir T promove os métodos com receiver de valor de T tanto para o valor externo quanto para o ponteiro externo, mas os métodos com receiver ponteiro de *T só para o ponteiro externo. Embutir *T promove os dois conjuntos para os dois. Na dúvida, embuta o ponteiro, ou use o tipo externo por meio de um ponteiro.
Embutir um ponteiro tem um custo: o ponteiro pode ser nil. Service{} sem Logger compila, e svc.Log(...) então causa panic.
Embedding não é herança
O embedding parece subclasse, mas três coisas se comportam diferente de Java ou Python.
O tipo externo não é o tipo interno. Um Admin não é um User. Uma função que recebe um User não aceita um Admin; passe a.User.
Não existe despacho virtual. Um método promovido executa sobre o valor embutido e não sabe nada do tipo externo. Se ele chamar outro método, chama a versão do próprio tipo, mesmo quando o tipo externo define um com o mesmo nome.
Saída:
woof
The animal says ...
Em uma linguagem baseada em classes, Speak imprimiria "woof". Em Go, d.Speak() é um atalho para d.Animal.Speak(), e o receiver dessa chamada é o Animal. Quando você quer um comportamento que varie por tipo, use uma interface: passe um Sounder para o código que precisa dele.
O tipo interno não sabe que está embutido. Não existe super. Para estender um método promovido, defina um método com o mesmo nome no tipo externo e chame o interno explicitamente:
func (a Admin) Greet() string {
return a.User.Greet() + " (admin)"
}
Conflitos de nome e sombreamento
Um campo ou método do tipo externo sombreia um promovido com o mesmo nome, como Dog.Sound fez acima. O nome mais raso vence.
Quando dois tipos embutidos na mesma profundidade promovem o mesmo nome, esse nome fica ambíguo. O programa continua compilando desde que nada use o nome ambíguo:
Resolva o conflito usando o caminho completo, ou definindo ID no tipo externo.
Embedding de interfaces
Interfaces podem embutir outras interfaces. A biblioteca padrão monta interfaces maiores desse jeito:
type ReadWriter interface {
Reader
Writer
}
Uma struct também pode embutir uma interface. A struct então satisfaz essa interface por meio do valor que você guardar no campo, e você sobrescreve só os métodos que importam. É o padrão decorator com quase nenhum código:
O c.Reader.Read(p) explícito é obrigatório. Escrever c.Read(p) dentro de Read chamaria a si mesmo para sempre.
Embutir uma interface em um fake de teste é um atalho comum: embuta a interface grande, implemente o único método de que o teste precisa e deixe o resto. Qualquer método não implementado causa panic de ponteiro nil se for chamado, o que muitas vezes é exatamente o que você quer em um teste.
Um uso real comum: embutir sync.Mutex
type Stats struct {
sync.Mutex
hits map[string]int
}
func (s *Stats) Hit(page string) {
s.Lock()
defer s.Unlock()
s.hits[page]++
}
Isso fica legível, mas também exporta Lock e Unlock como parte da API de Stats, então qualquer código que a use pode travar a sua struct. Para tipos usados fora do pacote, um campo nomeado (mu sync.Mutex) mantém o lock privado. Esse dilema vale para todo embedding: tudo o que o tipo embutido exporta passa a fazer parte da superfície pública do seu tipo.
Erros comuns
- Esperar despacho virtual. Métodos promovidos nunca chamam as versões sobrescritas do tipo externo.
- Definir campos promovidos em um literal. Use o nome do tipo embutido:
Admin{User: User{Name: "Ana"}}. - Ponteiros embutidos nil. Um
*Tou uma interface embutidos precisam ser definidos antes de os métodos serem chamados. - API exposta sem querer. O embedding exporta no seu tipo todos os métodos exportados do tipo interno.
Perguntas frequentes
O que é embedding de structs em Go?
Declarar um campo só com o tipo, sem nome: type Admin struct { User; Level int }. Os campos e métodos do User embutido são promovidos, então a.Name e a.Greet() funcionam diretamente em um Admin. O valor embutido continua sendo um campo normal, acessível como a.User.
Go tem herança?
Não. Go não tem classes nem herança de subtipos. O embedding dá reuso de código por composição: o tipo externo ganha os métodos do tipo interno, mas um Admin não é um User. Você não pode passar um Admin onde se espera um User, e métodos promovidos não conseguem chamar de volta os métodos do tipo externo. Em Go, o polimorfismo vem das interfaces.
Como inicializar uma struct embutida em Go?
Em um literal composto, identifique o campo embutido pelo nome do tipo: Admin{User: User{Name: "Ana"}, Level: 2}. Não dá para definir campos promovidos diretamente no literal: Admin{Name: "Ana"} falha com unknown field Name in struct literal of type Admin.
Dá para embutir uma interface em uma struct em Go?
Sim. A struct passa a satisfazer a interface por meio do valor embutido, e você pode sobrescrever métodos individuais. É comum em decorators e em fakes de teste. Se o campo de interface embutido for nil, chamar um método que você não sobrescreveu causa panic de desreferência de ponteiro nil.