Uma interface é um conjunto de métodos
Um tipo interface lista assinaturas de métodos. Qualquer tipo que tenha esses métodos satisfaz a interface, sem nenhuma declaração dizendo isso.
Nem Rect nem Circle mencionam Shape. Essa é a implementação implícita, a característica que define as interfaces em Go. Ela permite definir no seu pacote uma interface que tipos de outros pacotes já satisfazem, sem mexer no código deles.
Interfaces pequenas da biblioteca padrão
Código Go prefere interfaces com um ou dois métodos. As mais importantes:
| Interface | Método | Usada por |
|---|---|---|
fmt.Stringer | String() string | impressão do fmt |
error | Error() string | toda função que pode falhar |
io.Reader | Read(p []byte) (n int, err error) | arquivos, rede, gzip, corpos HTTP |
io.Writer | Write(p []byte) (n int, err error) | arquivos, buffers, hashes, respostas HTTP |
sort.Interface | Len, Less, Swap | o pacote sort |
http.Handler | ServeHTTP(w, r) | net/http |
Como io.Reader tem um só método, dezenas de tipos o implementam, e qualquer função que recebe um io.Reader funciona com todos eles:
O provérbio do Go diz: "quanto maior a interface, mais fraca a abstração". Interfaces maiores são montadas combinando as pequenas: io.ReadWriter é Reader mais Writer, escrita embutindo uma interface em outra.
any: a interface vazia
interface{} não tem métodos, então todo tipo a satisfaz. O Go 1.18 acrescentou any como alias; os dois são idênticos.
Um valor any pode guardar qualquer coisa, mas você não consegue fazer quase nada com ele até recuperar o tipo concreto com uma type assertion ou um type switch. Prefira uma interface de verdade ou generics quando o conjunto de tipos é conhecido. any serve para dados realmente dinâmicos, como um JSON decodificado de formato desconhecido, e para impressão.
O que um valor de interface contém
Um valor de interface é um par: um tipo dinâmico e um valor dinâmico. var s Shape = Rect{3, 4} guarda o tipo Rect e uma cópia do valor. Chamar s.Area() procura o método de Rect em tempo de execução.
Uma interface só é nil quando as duas partes estão vazias. Essa regra causa o bug mais confuso do Go.
A armadilha da interface nil
Um ponteiro nil guardado em uma interface gera uma interface que não é nil.
Saída:
false
*main.MyError true
true
validate(true) devolve uma interface error que guarda o tipo *MyError e o valor nil. A interface tem um tipo, então não é igual a nil, e o ramo if err != nil de quem chama executa. Chamar err.Error() ali causaria panic no acesso ao campo do receiver nil.
A correção é simples: declare a variável como error, não como o tipo ponteiro concreto, ou devolva um nil literal no caminho de sucesso. Nunca devolva um tipo ponteiro de erro concreto em uma função cujo resultado é error. A mesma armadilha vale para qualquer interface, não só para erros.
Verificando que um tipo implementa uma interface
A implementação é verificada onde um valor é atribuído a uma interface. Se nenhum código faz isso ainda, um erro em uma assinatura de método passa despercebido. Uma atribuição em branco no nível de pacote torna a verificação explícita:
var _ io.Writer = (*LogWriter)(nil)
var _ fmt.Stringer = Temp(0)
Isso não custa nada em tempo de execução. Se *LogWriter tiver Write(p []byte) error em vez de Write(p []byte) (int, error), o build falha:
cannot use (*LogWriter)(nil) (value of type *LogWriter) as io.Writer value in variable declaration: *LogWriter does not implement io.Writer (wrong type for method Write)
have Write([]byte) error
want Write([]byte) (int, error)
Receivers ponteiro e interfaces
Se um método tem receiver ponteiro, só o tipo ponteiro tem esse método. *Counter satisfaz uma interface por meio dele; Counter não. O compilador diz Counter does not implement Incrementer (method Inc has pointer receiver). Guarde &Counter{} na interface. A página de métodos explica os conjuntos de métodos.
Aceite interfaces, devolva structs
Uma diretriz comum em Go: funções devem receber parâmetros interface e devolver tipos concretos.
- Aceitar uma interface permite que quem chama passe qualquer coisa que se encaixe, inclusive fakes de teste. Uma função que lê dados deve receber um
io.Reader, não um*os.File. - Devolver um tipo concreto permite que quem chama use todos os métodos e campos dele, e evita a armadilha da interface nil.
os.Opendevolve*os.File, nãoio.Reader.
Um hábito relacionado: defina interfaces onde elas são usadas, não onde são implementadas. Se o seu serviço precisa de algo que consiga fazer Get(id) de um usuário, declare uma interface de um método no pacote do serviço e deixe o pacote do banco de dados apenas exportar a sua struct.
Comparando valores de interface
Dois valores de interface são iguais quando os tipos dinâmicos são idênticos e os valores dinâmicos são iguais. Se o tipo dinâmico não for comparável (um slice, um map), == compila, mas causa panic em tempo de execução: runtime error: comparing uncomparable type []int.
Erros comuns
- Devolver um ponteiro nil tipado como interface. Devolva
nilliteral. - Interfaces cedo demais. Escreva o tipo concreto primeiro. Acrescente uma interface quando uma segunda implementação ou um teste precisar.
- Ponteiro para interface.
*io.Readerquase nunca está certo. Uma interface já guarda um ponteiro quando você guarda um nela. - Interfaces grandes. Interfaces de dez métodos são difíceis de implementar e de simular em testes. Divida-as.
Perguntas frequentes
Como implementar uma interface em Go?
Defina no seu tipo os métodos que a interface lista, com os mesmos nomes e assinaturas. Não existe a palavra-chave implements. Se *File tem Read(p []byte) (int, error), ele é um io.Reader, automaticamente. O compilador verifica isso onde quer que você atribua o valor ao tipo interface.
O que é a interface vazia ou any em Go?
interface{} não tem métodos, então todo tipo a satisfaz. Desde o Go 1.18, any é um alias embutido de interface{}. Um valor do tipo any pode guardar qualquer coisa, mas você precisa de uma type assertion ou de um type switch para recuperar um tipo concreto.
Por que minha interface Go não é nil quando atribuí um ponteiro nil?
Um valor de interface guarda um tipo e um valor. Atribuir um *MyError nil a um error gera uma interface cujo tipo é *MyError e cujo valor é nil, e essa interface não é igual a nil. Devolva um nil literal em vez de um ponteiro nil tipado quando não houver erro.
Como verificar em tempo de compilação que um tipo implementa uma interface?
Acrescente uma atribuição em branco no nível de pacote: var _ io.Reader = (*MyReader)(nil). Se faltar um método em *MyReader, o build falha com uma mensagem que nomeia o método ausente.