Interfaces existem em Java, C#, TypeScript e diversas outras linguagens. Mas a implementação de interfaces em Go é radicalmente diferente: ela é implícita. Um tipo não precisa declarar que implementa uma interface. Se ele possui todos os métodos que a interface exige, ele a implementa automaticamente — sem herança, sem implements, sem nenhuma declaração formal.
Essa decisão de design tem consequências profundas. Ela desacopla completamente a definição de uma interface de sua implementação, permite que tipos de pacotes externos implementem interfaces definidas localmente, e torna o sistema de tipos extraordinariamente flexível sem sacrificar a segurança em tempo de compilação.
Definindo uma interface
Uma interface é um conjunto de assinaturas de métodos. Qualquer tipo que possua todos esses métodos satisfaz a interface:
type Forma interface {
Area() float64
Perimetro() float64
}
Nenhum tipo precisa declarar implements Forma. Se um tipo tem os métodos Area() float64 e Perimetro() float64, ele é uma Forma — automaticamente e sem qualquer declaração adicional.
Implementação implícita na prática
Criando dois tipos que satisfazem a interface Forma sem nunca mencionar a interface em suas definições:
package main
import (
"fmt"
"math"
)
type Forma interface {
Area() float64
Perimetro() float64
}
type Retangulo struct {
Largura, Altura float64
}
func (r Retangulo) Area() float64 {
return r.Largura * r.Altura
}
func (r Retangulo) Perimetro() float64 {
return 2 * (r.Largura + r.Altura)
}
type Circulo struct {
Raio float64
}
func (c Circulo) Area() float64 {
return math.Pi * c.Raio * c.Raio
}
func (c Circulo) Perimetro() float64 {
return 2 * math.Pi * c.Raio
}
func descreverForma(f Forma) {
fmt.Printf("Área: %.2f | Perímetro: %.2f\n", f.Area(), f.Perimetro())
}
func main() {
r := Retangulo{Largura: 4, Altura: 3}
c := Circulo{Raio: 5}
descreverForma(r) // Área: 12.00 | Perímetro: 14.00
descreverForma(c) // Área: 78.54 | Perímetro: 31.42
}
A função descreverForma aceita qualquer valor que satisfaça Forma. Ela não sabe — nem precisa saber — se está recebendo um Retangulo, um Circulo ou qualquer outro tipo futuro.
A interface vazia: interface{} e any
A interface vazia não exige nenhum método. Portanto, qualquer tipo a satisfaz. Em Go moderno, any é um alias para interface{} e é a forma preferida:
func imprimir(v any) {
fmt.Println(v)
}
func main() {
imprimir(42)
imprimir("texto")
imprimir(true)
imprimir([]int{1, 2, 3})
}
any é útil quando um valor de tipo genuinamente desconhecido precisa ser armazenado ou passado. Porém, seu uso excessivo é um sinal de design impreciso — quando o tipo é conhecido, interfaces específicas são sempre preferíveis.
Type assertion: recuperando o tipo concreto
Quando se tem um valor de tipo interface e precisa acessar o tipo concreto subjacente, usa-se a type assertion:
var f Forma = Circulo{Raio: 3}
// Forma segura — com comma ok
if c, ok := f.(Circulo); ok {
fmt.Println("É um círculo de raio:", c.Raio)
}
// Forma direta — causa pânico se o tipo não corresponder
c := f.(Circulo)
fmt.Println(c.Raio)
Sempre prefira a forma com ok a menos que a certeza sobre o tipo seja absoluta. A forma direta deve ser usada com cautela, pois um tipo incorreto causa pânico em tempo de execução.
Type switch: múltiplos tipos em um só bloco
Quando diferentes ações são necessárias para diferentes tipos concretos, o type switch é mais legível do que uma cadeia de type assertions:
package main
import "fmt"
func descrever(v any) {
switch val := v.(type) {
case int:
fmt.Printf("Inteiro: %d (dobro: %d)\n", val, val*2)
case string:
fmt.Printf("String: %q (tamanho: %d)\n", val, len(val))
case bool:
fmt.Printf("Booleano: %t\n", val)
case []int:
fmt.Printf("Slice de int com %d elementos\n", len(val))
case nil:
fmt.Println("Valor nulo")
default:
fmt.Printf("Tipo desconhecido: %T\n", val)
}
}
func main() {
descrever(42)
descrever("Go é elegante")
descrever(true)
descrever([]int{1, 2, 3})
descrever(nil)
descrever(3.14)
}
A variável val dentro de cada case já tem o tipo concreto correspondente — não é necessário fazer uma nova type assertion.
Interfaces da biblioteca padrão: o poder do design implícito
Algumas das interfaces mais importantes do Go são definidas na biblioteca padrão com apenas um ou dois métodos. Sua simplicidade é o que as torna universais.
fmt.Stringer — para representação textual:
type Stringer interface {
String() string
}
error — a interface de erros do Go:
type error interface {
Error() string
}
io.Reader e io.Writer — para leitura e escrita de dados:
type Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}
Qualquer tipo que implemente Read pode ser usado onde um io.Reader é esperado — seja um arquivo em disco, uma conexão de rede, um buffer em memória ou um leitor de strings. Todo o sistema de I/O do Go é construído sobre essas duas interfaces simples.
Implementando a interface error
Criar tipos de erro personalizados é uma prática comum e poderosa em Go:
package main
import "fmt"
type ErroDivisao struct {
Dividendo float64
Divisor float64
}
func (e *ErroDivisao) Error() string {
return fmt.Sprintf("não é possível dividir %.2f por %.2f", e.Dividendo, e.Divisor)
}
func dividir(a, b float64) (float64, error) {
if b == 0 {
return 0, &ErroDivisao{Dividendo: a, Divisor: b}
}
return a / b, nil
}
func main() {
resultado, err := dividir(10, 0)
if err != nil {
fmt.Println("Erro:", err)
// Recuperando informações específicas do tipo de erro
if e, ok := err.(*ErroDivisao); ok {
fmt.Printf("Tentou dividir %.0f por %.0f\n", e.Dividendo, e.Divisor)
}
return
}
fmt.Println(resultado)
}
Composição de interfaces
Assim como structs podem embutir outras structs, interfaces podem embutir outras interfaces. Isso permite construir contratos maiores a partir de contratos menores:
type Leitor interface {
Ler() ([]byte, error)
}
type Escritor interface {
Escrever(dados []byte) error
}
type LeitorEscritor interface {
Leitor
Escritor
}
Um tipo que implementa tanto Ler quanto Escrever automaticamente satisfaz LeitorEscritor. A biblioteca padrão usa esse padrão extensivamente — io.ReadWriter, io.ReadWriteCloser e outros são composições de interfaces menores.
Interfaces e nil: uma armadilha importante
Um valor de interface em Go é internamente composto por dois campos: o tipo concreto e o ponteiro para o valor. Uma interface é nil apenas quando ambos são nil. Essa distinção cria uma armadilha clássica:
package main
import "fmt"
type MinhaInterface interface {
Fazer()
}
type MeuTipo struct{}
func (m *MeuTipo) Fazer() {}
func retornarInterface(retornarNil bool) MinhaInterface {
var p *MeuTipo // ponteiro nil do tipo *MeuTipo
if retornarNil {
return nil // interface genuinamente nil
}
return p // interface NÃO nil — tem tipo, mas valor nil
}
func main() {
i1 := retornarInterface(true)
i2 := retornarInterface(false)
fmt.Println(i1 == nil) // true
fmt.Println(i2 == nil) // false — armadilha!
}
No segundo caso, a interface contém informação de tipo (*MeuTipo) mesmo que o valor seja nil. A verificação == nil retorna false. Para evitar esse problema, nunca retorne uma variável tipada como nil quando o retorno esperado é uma interface — retorne nil diretamente.
Interfaces pequenas são melhores
Um princípio central do design Go é que interfaces devem ser pequenas. A comunidade consolidou o ditado: "The bigger the interface, the weaker the abstraction." — Rob Pike.
Uma interface com dez métodos é difícil de satisfazer e difícil de substituir. Uma interface com um método é trivial de implementar, de testar com mocks e de compor com outras interfaces.
// Difícil de satisfazer — acoplamento alto
type ServicoCompleto interface {
Criar(dados any) error
Ler(id int) (any, error)
Atualizar(id int, dados any) error
Deletar(id int) error
Listar() ([]any, error)
Buscar(query string) ([]any, error)
Exportar(formato string) ([]byte, error)
}
// Fácil de satisfazer — acoplamento baixo
type Criador interface {
Criar(dados any) error
}
type Leitor interface {
Ler(id int) (any, error)
}
Quando uma função precisa apenas criar registros, ela deve receber um Criador — não um ServicoCompleto. Isso facilita testes, mocks e substituição de implementações.
Exemplo completo: sistema de notificações
Consolidando os conceitos em um exemplo prático que demonstra polimorfismo real:
package main
import "fmt"
type Notificador interface {
Enviar(destinatario, mensagem string) error
Nome() string
}
type Email struct {
Servidor string
}
func (e Email) Enviar(dest, msg string) error {
fmt.Printf("[EMAIL via %s] Para: %s | %s\n", e.Servidor, dest, msg)
return nil
}
func (e Email) Nome() string { return "Email" }
type SMS struct {
Operadora string
}
func (s SMS) Enviar(dest, msg string) error {
fmt.Printf("[SMS via %s] Para: %s | %s\n", s.Operadora, dest, msg)
return nil
}
func (s SMS) Nome() string { return "SMS" }
type Push struct{}
func (p Push) Enviar(dest, msg string) error {
fmt.Printf("[PUSH] Para: %s | %s\n", dest, msg)
return nil
}
func (p Push) Nome() string { return "Push" }
type ServicoNotificacao struct {
canais []Notificador
}
func (sn *ServicoNotificacao) AdicionarCanal(n Notificador) {
sn.canais = append(sn.canais, n)
}
func (sn *ServicoNotificacao) NotificarTodos(dest, msg string) {
for _, canal := range sn.canais {
if err := canal.Enviar(dest, msg); err != nil {
fmt.Printf("Erro no canal %s: %v\n", canal.Nome(), err)
}
}
}
func main() {
servico := &ServicoNotificacao{}
servico.AdicionarCanal(Email{Servidor: "smtp.exemplo.com"})
servico.AdicionarCanal(SMS{Operadora: "Vivo"})
servico.AdicionarCanal(Push{})
servico.NotificarTodos("usuario@exemplo.com", "Seu pedido foi aprovado!")
}
Resumo do que foi coberto
Este artigo apresentou interfaces em Go com profundidade: implementação implícita, a interface vazia any, type assertions, type switch, interfaces essenciais da biblioteca padrão, criação de erros personalizados, composição de interfaces, a armadilha do nil em interfaces e o princípio de interfaces pequenas. Com structs, métodos e interfaces dominados, o próximo artigo explora como Go usa composição para substituir herança.
Referências e leituras complementares
-
Especificação da linguagem Go — Interface types — Definição formal de interfaces e satisfação implícita. https://go.dev/ref/spec#Interface_types
-
A Tour of Go — Interfaces — Introdução interativa com exemplos progressivos. https://go.dev/tour/methods/9
-
Go by Example: Interfaces — Exemplos práticos comentados. https://gobyexample.com/interfaces
-
Effective Go — Interfaces — Filosofia e boas práticas oficiais para design de interfaces. https://go.dev/doc/effective_go#interfaces
-
Go Blog: The Laws of Reflection — Como interfaces e reflection se relacionam internamente. https://go.dev/blog/laws-of-reflection
-
Go Blog: Errors are values — Rob Pike sobre o design do sistema de erros baseado em interfaces. https://go.dev/blog/errors-are-values