Janeiro 2026 · ~16 min

Interfaces em Go (Golang): exemplos, receivers e nil

Aprenda interfaces em Go (Golang): implementação implícita, receivers, any e type assertions. Veja por que uma interface com ponteiro nil pode não ser nil.

Interfaces em Go (Golang): exemplos, receivers e nil

Uma interface em Go define um contrato de comportamento por meio de métodos. Um tipo a implementa implicitamente quando seu conjunto de métodos inclui todos os métodos exigidos — não existe a declaração implements. Go é o nome oficial da linguagem; Golang é seu apelido popular. Isso permite receber comportamentos como io.Reader ou fmt.Stringer sem acoplar a função a uma struct específica. A regra formal está na especificação de interfaces e method sets; na prática, prefira interfaces pequenas, definidas onde são consumidas e criadas para uma necessidade concreta.

Este guia explica interfaces em Go com exemplos de implementação implícita, any, type assertions, composição, receivers de valor e ponteiro, testes com fakes e padrões usados em projetos reais. Se sua dúvida é por que err != nil mesmo quando o ponteiro é nulo, vá à seção sobre interfaces e nil.


O que é uma interface em Go?

Uma interface define um contrato de comportamento: ela especifica quais métodos um tipo deve ter, mas não determina como implementá-los. Pela especificação da linguagem, um tipo implementa uma interface quando pertence ao conjunto de tipos descrito por ela; em interfaces básicas, isso significa possuir todos os métodos exigidos:

// Definição de interface: "qualquer coisa que saiba falar"
type Falante interface {
    Falar() string
}

Qualquer tipo com um método Falar() string satisfaz automaticamente essa interface:

// Cachorro implementa Falante (sem declarar isso explicitamente!)
type Cachorro struct {
    Nome string
}

func (c Cachorro) Falar() string {
    return c.Nome + " diz: Au au!"
}

// Gato também implementa Falante
type Gato struct {
    Nome string
}

func (g Gato) Falar() string {
    return g.Nome + " diz: Miau!"
}

// Papagaio também implementa Falante
type Papagaio struct {
    Nome  string
    Frase string
}

func (p Papagaio) Falar() string {
    return p.Nome + " repete: " + p.Frase
}

Agora podemos usar qualquer um deles onde se espera um Falante:

// cumprimentar aceita QUALQUER Falante
func cumprimentar(f Falante) {
    fmt.Println(f.Falar())
}

func main() {
    cumprimentar(Cachorro{Nome: "Rex"})
    cumprimentar(Gato{Nome: "Mimi"})
    cumprimentar(Papagaio{Nome: "Loro", Frase: "Oi, bonito!"})
}
// Saída:
// Rex diz: Au au!
// Mimi diz: Miau!
// Loro repete: Oi, bonito!

Observe: em nenhum lugar escrevemos Cachorro implements Falante. O compilador compara os métodos automaticamente. Essa é a implementação implícita.


Por que Implementação Implícita?

Na maioria das linguagens, você precisa declarar explicitamente quais interfaces um tipo implementa:

// Java — implementação explícita
class Cachorro implements Falante {
    public String falar() { return "Au au!"; }
}

Em Go, não. E isso traz vantagens enormes:

  1. Desacoplamento real: o tipo não precisa “saber” sobre a interface
  2. Interfaces podem ser definidas pelo consumidor: quem usa o código define a interface, não quem implementa
  3. Retroatividade: tipos existentes satisfazem interfaces criadas depois
  4. Sem hierarquias rígidas: não há herança de interfaces forçada

Interfaces da Biblioteca Padrão

A stdlib do Go é rica em interfaces pequenas e poderosas. Conhecê-las é essencial:

io.Reader e io.Writer

// io.Reader — qualquer coisa de onde se pode ler bytes
type Reader interface {
    Read(p []byte) (n int, err error)
}

// io.Writer — qualquer coisa para onde se pode escrever bytes
type Writer interface {
    Write(p []byte) (n int, err error)
}

Quem implementa io.Reader? Arquivos, conexões de rede, buffers, compressores, corpo de requisições HTTP e muito mais. Essa é a beleza:

package main

import (
    "fmt"
    "io"
    "os"
    "strings"
)

// contarBytes funciona com QUALQUER io.Reader
func contarBytes(r io.Reader) (int64, error) {
    return io.Copy(io.Discard, r)
}

func main() {
    // Funciona com string
    n, _ := contarBytes(strings.NewReader("Olá, Go!"))
    fmt.Printf("String: %d bytes\n", n)

    // Funciona com arquivo
    arquivo, _ := os.Open("main.go")
    defer arquivo.Close()
    n, _ = contarBytes(arquivo)
    fmt.Printf("Arquivo: %d bytes\n", n)

    // Funciona com stdin
    // n, _ = contarBytes(os.Stdin)
}

fmt.Stringer

// fmt.Stringer — controla como um tipo é impresso
type Stringer interface {
    String() string
}
type Moeda struct {
    Valor  float64
    Codigo string
}

// Implementa fmt.Stringer
func (m Moeda) String() string {
    return fmt.Sprintf("%s %.2f", m.Codigo, m.Valor)
}

func main() {
    preco := Moeda{Valor: 49.90, Codigo: "BRL"}
    fmt.Println(preco) // BRL 49.90 (usa String() automaticamente)
}

error

A interface mais famosa do Go:

type error interface {
    Error() string
}

Qualquer tipo com Error() string é um erro. Já exploramos isso em detalhes no guia de tratamento de erros.

sort.Interface

type Interface interface {
    Len() int
    Less(i, j int) bool
    Swap(i, j int)
}
type PorIdade []Pessoa

func (p PorIdade) Len() int           { return len(p) }
func (p PorIdade) Less(i, j int) bool { return p[i].Idade < p[j].Idade }
func (p PorIdade) Swap(i, j int)      { p[i], p[j] = p[j], p[i] }

func main() {
    pessoas := []Pessoa{
        {"Alice", 30},
        {"Bob", 25},
        {"Carol", 35},
    }
    sort.Sort(PorIdade(pessoas))
    // Bob (25), Alice (30), Carol (35)
}

Interface Vazia: any (interface{})

A interface vazia não tem métodos, portanto pode receber valores de qualquer tipo. Desde o Go 1.18, o identificador predeclarado any é um alias de interface{}:

var valorAntigo interface{}
var valorAtual any // equivalente a interface{}

Use com moderação. any continua sujeito às regras de tipos do Go, mas não expõe métodos específicos: para acessar o valor como um tipo concreto, você precisa de uma type assertion ou de um type switch.

func imprimir(valor any) {
    fmt.Printf("Tipo: %T, Valor: %v\n", valor, valor)
}

func main() {
    imprimir(42)           // Tipo: int, Valor: 42
    imprimir("texto")      // Tipo: string, Valor: texto
    imprimir(true)         // Tipo: bool, Valor: true
    imprimir([]int{1, 2})  // Tipo: []int, Valor: [1 2]
}

Quando usar any:

  • Funções genéricas como fmt.Println
  • Containers genéricos (antes dos generics do Go 1.18)
  • Decodificação de JSON dinâmico

Quando NÃO usar:

  • Quando você sabe o tipo: use o tipo concreto
  • Quando precisa de comportamento: use uma interface com métodos

Type Assertions: Recuperando o Tipo Concreto

Quando você tem um valor de interface, pode descobrir ou afirmar o tipo concreto:

// Type assertion básica
var f Falante = Cachorro{Nome: "Rex"}

// Afirma que f é um Cachorro
cachorro, ok := f.(Cachorro)
if ok {
    fmt.Println("É um cachorro:", cachorro.Nome)
} else {
    fmt.Println("Não é um cachorro")
}

// Type assertion sem verificação (PERIGOSO — causa panic se falhar!)
cachorro2 := f.(Cachorro) // panic se f não for Cachorro

Quando o tipo concreto não é garantido, prefira a forma com dois retornos (valor, ok) para tratar a falha sem panic. A forma de um retorno é apropriada apenas quando a invariável já garante o tipo e um erro de programação deve interromper a execução.

Type Switch: Múltiplos Tipos

O type switch é elegante para lidar com múltiplos tipos possíveis:

func descrever(i any) string {
    switch v := i.(type) {
    case int:
        return fmt.Sprintf("Inteiro: %d", v)
    case string:
        return fmt.Sprintf("String: %q (comprimento: %d)", v, len(v))
    case bool:
        if v {
            return "Booleano: verdadeiro"
        }
        return "Booleano: falso"
    case []int:
        return fmt.Sprintf("Slice de inteiros com %d elementos", len(v))
    case nil:
        return "Nulo"
    default:
        return fmt.Sprintf("Tipo desconhecido: %T", v)
    }
}

func main() {
    fmt.Println(descrever(42))           // Inteiro: 42
    fmt.Println(descrever("Go"))         // String: "Go" (comprimento: 2)
    fmt.Println(descrever(true))         // Booleano: verdadeiro
    fmt.Println(descrever([]int{1, 2}))  // Slice de inteiros com 2 elementos
}

Composição de Interfaces

Em Go, interfaces podem compor (embutir) outras interfaces:

// Interfaces pequenas e focadas
type Leitor interface {
    Ler(p []byte) (int, error)
}

type Escritor interface {
    Escrever(p []byte) (int, error)
}

type Fechador interface {
    Fechar() error
}

// Composição: combina múltiplas interfaces
type LeitorEscritor interface {
    Leitor
    Escritor
}

type LeitorFechador interface {
    Leitor
    Fechador
}

type LeitorEscritorFechador interface {
    Leitor
    Escritor
    Fechador
}

A biblioteca padrão usa esse padrão extensivamente:

// io.ReadWriter = io.Reader + io.Writer
type ReadWriter interface {
    Reader
    Writer
}

// io.ReadCloser = io.Reader + io.Closer
type ReadCloser interface {
    Reader
    Closer
}

// io.ReadWriteCloser = io.Reader + io.Writer + io.Closer
type ReadWriteCloser interface {
    Reader
    Writer
    Closer
}

Padrão: Aceite Interfaces, Retorne Structs

Este é um dos princípios mais importantes do design em Go:

// ✅ BOM: aceita interface (flexível)
func salvarDados(w io.Writer, dados []byte) error {
    _, err := w.Write(dados)
    return err
}

// ❌ RUIM: aceita tipo concreto (rígido)
func salvarDados(f *os.File, dados []byte) error {
    _, err := f.Write(dados)
    return err
}

A versão que aceita io.Writer funciona com arquivos, buffers, conexões de rede, HTTP responses e qualquer outra coisa que implemente Write. A que aceita *os.File só funciona com arquivos.

// ✅ BOM: retorna struct (concreto, informativo)
func NovoServidor(config Config) *Servidor {
    return &Servidor{config: config}
}

// ❌ RUIM: retorna interface (esconde o tipo real sem necessidade)
func NovoServidor(config Config) ServidorInterface {
    return &Servidor{config: config}
}

Padrão do Mundo Real: Repository Pattern

O Repository Pattern é perfeito para interfaces em Go:

// Defina a interface pelo consumidor (não pelo implementador)
type RepositorioUsuario interface {
    BuscarPorID(ctx context.Context, id int64) (*Usuario, error)
    BuscarPorEmail(ctx context.Context, email string) (*Usuario, error)
    Criar(ctx context.Context, u *Usuario) error
    Atualizar(ctx context.Context, u *Usuario) error
    Deletar(ctx context.Context, id int64) error
}

// Implementação com PostgreSQL
type postgresUsuarioRepo struct {
    db *sql.DB
}

func NovoPostgresUsuarioRepo(db *sql.DB) RepositorioUsuario {
    return &postgresUsuarioRepo{db: db}
}

func (r *postgresUsuarioRepo) BuscarPorID(ctx context.Context, id int64) (*Usuario, error) {
    var u Usuario
    err := r.db.QueryRowContext(ctx,
        "SELECT id, nome, email FROM usuarios WHERE id = $1", id,
    ).Scan(&u.ID, &u.Nome, &u.Email)
    if err == sql.ErrNoRows {
        return nil, ErrNaoEncontrado
    }
    return &u, err
}

func (r *postgresUsuarioRepo) BuscarPorEmail(ctx context.Context, email string) (*Usuario, error) {
    var u Usuario
    err := r.db.QueryRowContext(ctx,
        "SELECT id, nome, email FROM usuarios WHERE email = $1", email,
    ).Scan(&u.ID, &u.Nome, &u.Email)
    if err == sql.ErrNoRows {
        return nil, ErrNaoEncontrado
    }
    return &u, err
}

func (r *postgresUsuarioRepo) Criar(ctx context.Context, u *Usuario) error {
    _, err := r.db.ExecContext(ctx,
        "INSERT INTO usuarios (nome, email) VALUES ($1, $2)", u.Nome, u.Email,
    )
    return err
}

func (r *postgresUsuarioRepo) Atualizar(ctx context.Context, u *Usuario) error {
    _, err := r.db.ExecContext(ctx,
        "UPDATE usuarios SET nome=$1, email=$2 WHERE id=$3", u.Nome, u.Email, u.ID,
    )
    return err
}

func (r *postgresUsuarioRepo) Deletar(ctx context.Context, id int64) error {
    _, err := r.db.ExecContext(ctx,
        "DELETE FROM usuarios WHERE id = $1", id,
    )
    return err
}

// O serviço depende da INTERFACE, não da implementação
type ServicoUsuario struct {
    repo RepositorioUsuario // Interface, não *postgresUsuarioRepo
}

func NovoServicoUsuario(repo RepositorioUsuario) *ServicoUsuario {
    return &ServicoUsuario{repo: repo}
}

func (s *ServicoUsuario) ObterUsuario(ctx context.Context, id int64) (*Usuario, error) {
    return s.repo.BuscarPorID(ctx, id)
}

Testando com Interfaces: Mocks

A grande vantagem de depender de interfaces é a facilidade para testar:

// Mock do repositório para testes
type mockUsuarioRepo struct {
    usuarios map[int64]*Usuario
    erroBusca error
}

func novoMockRepo() *mockUsuarioRepo {
    return &mockUsuarioRepo{
        usuarios: make(map[int64]*Usuario),
    }
}

func (m *mockUsuarioRepo) BuscarPorID(ctx context.Context, id int64) (*Usuario, error) {
    if m.erroBusca != nil {
        return nil, m.erroBusca
    }
    u, existe := m.usuarios[id]
    if !existe {
        return nil, ErrNaoEncontrado
    }
    return u, nil
}

func (m *mockUsuarioRepo) BuscarPorEmail(ctx context.Context, email string) (*Usuario, error) {
    for _, u := range m.usuarios {
        if u.Email == email {
            return u, nil
        }
    }
    return nil, ErrNaoEncontrado
}

func (m *mockUsuarioRepo) Criar(ctx context.Context, u *Usuario) error {
    m.usuarios[u.ID] = u
    return nil
}

func (m *mockUsuarioRepo) Atualizar(ctx context.Context, u *Usuario) error {
    m.usuarios[u.ID] = u
    return nil
}

func (m *mockUsuarioRepo) Deletar(ctx context.Context, id int64) error {
    delete(m.usuarios, id)
    return nil
}

// Agora o teste usa o mock em vez do banco real
func TestObterUsuario(t *testing.T) {
    // Arrange
    mock := novoMockRepo()
    mock.usuarios[1] = &Usuario{ID: 1, Nome: "João", Email: "[email protected]"}

    servico := NovoServicoUsuario(mock)

    // Act
    usuario, err := servico.ObterUsuario(context.Background(), 1)

    // Assert
    if err != nil {
        t.Fatalf("erro inesperado: %v", err)
    }
    if usuario.Nome != "João" {
        t.Errorf("nome esperado 'João', obteve '%s'", usuario.Nome)
    }
}

func TestObterUsuarioNaoEncontrado(t *testing.T) {
    mock := novoMockRepo()
    servico := NovoServicoUsuario(mock)

    _, err := servico.ObterUsuario(context.Background(), 999)
    if !errors.Is(err, ErrNaoEncontrado) {
        t.Errorf("esperava ErrNaoEncontrado, obteve: %v", err)
    }
}

Padrão Strategy com Interfaces

Interfaces permitem trocar algoritmos em tempo de execução:

// Interface de notificação
type Notificador interface {
    Enviar(destinatario string, mensagem string) error
}

// Implementação: Email
type NotificadorEmail struct {
    SMTPHost string
    SMTPPort int
}

func (n *NotificadorEmail) Enviar(destinatario, mensagem string) error {
    fmt.Printf("Enviando email para %s: %s\n", destinatario, mensagem)
    // Lógica real de envio de email
    return nil
}

// Implementação: SMS
type NotificadorSMS struct {
    APIKey string
}

func (n *NotificadorSMS) Enviar(destinatario, mensagem string) error {
    fmt.Printf("Enviando SMS para %s: %s\n", destinatario, mensagem)
    // Lógica real de envio de SMS
    return nil
}

// Implementação: Push notification
type NotificadorPush struct {
    AppID string
}

func (n *NotificadorPush) Enviar(destinatario, mensagem string) error {
    fmt.Printf("Enviando push para %s: %s\n", destinatario, mensagem)
    return nil
}

// Serviço que usa a interface (não sabe qual implementação vai receber)
type ServicoPedido struct {
    notificador Notificador
}

func (s *ServicoPedido) ConfirmarPedido(pedidoID int, clienteContato string) error {
    // Lógica de negócio...
    msg := fmt.Sprintf("Pedido #%d confirmado com sucesso!", pedidoID)
    return s.notificador.Enviar(clienteContato, msg)
}

func main() {
    // Fácil trocar a estratégia de notificação
    servicoEmail := &ServicoPedido{
        notificador: &NotificadorEmail{SMTPHost: "smtp.gmail.com", SMTPPort: 587},
    }
    servicoEmail.ConfirmarPedido(123, "[email protected]")

    servicoSMS := &ServicoPedido{
        notificador: &NotificadorSMS{APIKey: "abc123"},
    }
    servicoSMS.ConfirmarPedido(456, "+5511999999999")
}

Armadilha: Pointer Receivers vs Value Receivers

Esta é uma das confusões mais comuns em Go:

type Contador struct {
    valor int
}

// Método com VALUE receiver
func (c Contador) Valor() int {
    return c.valor
}

// Método com POINTER receiver
func (c *Contador) Incrementar() {
    c.valor++
}

type Incrementavel interface {
    Incrementar()
    Valor() int
}

func main() {
    // ✅ FUNCIONA: ponteiro satisfaz interface com pointer receiver
    var i Incrementavel = &Contador{}
    i.Incrementar()
    fmt.Println(i.Valor()) // 1

    // ❌ NÃO COMPILA: valor NÃO satisfaz interface com pointer receiver
    // var i2 Incrementavel = Contador{} // ERRO de compilação!
    // cannot use Contador{} as Incrementavel: Contador does not implement Incrementavel
    // (method Incrementar has pointer receiver)
}

Regra: se qualquer método da interface usa pointer receiver (*T), apenas *T (ponteiro) satisfaz a interface. Valor T não satisfaz.

Métodos do tipoT satisfaz?*T satisfaz?
Todos com value receiverSimSim
Algum com pointer receiverNaoSim

Armadilha: interface com ponteiro nil

Uma interface guarda um tipo dinâmico e um valor dinâmico. Ela só é igual a nil quando ambos estão ausentes. Se você atribui um ponteiro nulo a uma interface, ela passa a ter um tipo dinâmico, mesmo que o valor continue nulo. Essa distinção está documentada na FAQ oficial de Go sobre erros nil.

SituaçãoTipo dinâmicoValor dinâmicoInterface == nil?
var err errorAusenteAusenteSim
var p *ErroValidacao; var err error = p*ErroValidacaoPonteiro nilNão
var err error = &ErroValidacao{}*ErroValidacaoPonteiro não nuloNão

Exemplo completo: o erro que parece nil

Salve como main.go e execute go run main.go. A declaração var _ error = (*ErroValidacao)(nil) também verifica, em tempo de compilação, que o ponteiro implementa error.

package main

import "fmt"

type ErroValidacao struct {
    Campo string
}

func (e *ErroValidacao) Error() string {
    return "campo inválido: " + e.Campo
}

var _ error = (*ErroValidacao)(nil)

// Incorreto: retorna uma interface com tipo dinâmico *ErroValidacao.
func validarIncorreto() error {
    var e *ErroValidacao
    return e
}

func validarNome(nome string) error {
    if nome == "" {
        return &ErroValidacao{Campo: "nome"}
    }
    // Correto: retorna uma interface nil, sem tipo dinâmico.
    return nil
}

func main() {
    var vazio error
    var ponteiro *ErroValidacao
    var comTipo error = ponteiro

    fmt.Println("interface sem valor:", vazio == nil)
    fmt.Println("ponteiro nil:", ponteiro == nil)
    fmt.Println("interface com ponteiro nil:", comTipo == nil)
    fmt.Println("retorno incorreto:", validarIncorreto() == nil)
    fmt.Println("retorno correto:", validarNome("Ana") == nil)
    fmt.Println("nome vazio:", validarNome(""))
}

Saída esperada:

interface sem valor: true
ponteiro nil: true
interface com ponteiro nil: false
retorno incorreto: false
retorno correto: true
nome vazio: campo inválido: nome

Como evitar: em funções que retornam error, use return nil no caminho de sucesso, em vez de retornar uma variável de ponteiro que pode ser nula. Não resolva isso chamando err.Error() para verificar se houve erro: no exemplo, o método acessa e.Campo e causaria panic com um receiver nulo. Um método com receiver de ponteiro pode tratar nil explicitamente, mas a interface continuará diferente de nil.

A especificação sobre operadores de comparação define a comparação de interfaces. Para tratar erros reais, consulte também o guia de tratamento de erros em Go.


Interfaces Pequenas: O Segredo do Design em Go

A filosofia do Go favorece interfaces pequenas e focadas. Compare:

// ❌ Interface grande e genérica (fraca abstração)
type Banco interface {
    Conectar() error
    Desconectar() error
    Consultar(query string) (*Resultado, error)
    Inserir(tabela string, dados map[string]any) error
    Atualizar(tabela string, id int, dados map[string]any) error
    Deletar(tabela string, id int) error
    IniciarTransacao() (*Transacao, error)
    Migrar() error
    Backup() error
}

// ✅ Interfaces pequenas e compostas (forte abstração)
type Consultor interface {
    Consultar(ctx context.Context, query string, args ...any) (*Resultado, error)
}

type Executor interface {
    Executar(ctx context.Context, query string, args ...any) (sql.Result, error)
}

// Compõe quando necessário
type ConsultorExecutor interface {
    Consultor
    Executor
}

As interfaces da stdlib seguem essa filosofia rigorosamente:

  • io.Reader: 1 método
  • io.Writer: 1 método
  • io.Closer: 1 método
  • fmt.Stringer: 1 método
  • error: 1 método
  • sort.Interface: 3 métodos (e já é considerada “grande”)

Conclusão

Interfaces em Go são fundamentalmente diferentes de outras linguagens, e essa diferença é proposital. A implementação implícita, combinada com a filosofia de interfaces pequenas, cria um sistema de tipos extraordinariamente flexível.

Pontos essenciais:

  • Implementação implícita: sem implements, o compilador verifica automaticamente
  • Interfaces pequenas: 1-3 métodos é o ideal
  • Aceite interfaces, retorne structs: maximiza flexibilidade e clareza
  • Defina interfaces pelo consumidor: quem usa define o contrato
  • Composição sobre herança: combine interfaces pequenas para criar maiores
  • Teste com mocks: interfaces tornam o código testável
  • Cuidado com pointer receivers: afetam qual tipo satisfaz a interface
  • Use any com moderação: prefira interfaces com métodos

A recomendação de manter interfaces pequenas também aparece no Effective Go, que destaca interfaces de um ou dois métodos como convenções comuns. Defina o menor contrato que o consumidor realmente precisa e deixe o compilador verificar as implementações. Para comparar stacks — não só a sintaxe de interfaces versus traits — use o guia Go vs Rust e veja como Rust resolve polimorfismo com traits (implementação explícita + métodos padrão).


Perguntas frequentes sobre interfaces em Go

O que é uma interface em Go?

É um tipo que descreve um conjunto de métodos. Um valor pode ser usado como essa interface quando o method set do seu tipo contém todos os métodos exigidos. A implementação é implícita: não há palavra-chave implements.

Go e Golang são linguagens diferentes?

Não. Go é o nome oficial e Golang é o apelido usado em buscas, derivado do antigo domínio golang.org. Portanto, “interfaces Go” e “interfaces Golang” descrevem o mesmo recurso.

Qual é a diferença entre any e uma interface com métodos?

any é apenas um alias de interface{} e pode armazenar valores de qualquer tipo. Uma interface com métodos, como io.Reader, exige um comportamento específico e permite que o compilador verifique esse contrato.

Quando devo criar uma interface?

Crie uma interface quando o código consumidor realmente precisa aceitar implementações diferentes do mesmo comportamento. Casos comuns incluem entrada e saída com io.Reader e io.Writer, substituição de armazenamento em testes e integração com serviços externos. Se existe apenas um tipo concreto e nenhuma fronteira de substituição, começar sem interface costuma ser mais simples.

Um valor e um ponteiro implementam a mesma interface?

Nem sempre. Se todos os métodos necessários têm receiver de valor, T e *T podem satisfazer a interface. Se algum método necessário tem receiver de ponteiro, apenas *T o inclui em seu method set. Consulte a regra oficial em Method sets.

Por que uma interface com ponteiro nil não é nil?

Porque ela ainda possui um tipo dinâmico. Por exemplo, ao atribuir um *ErroValidacao nulo a uma variável error, a interface guarda o tipo *ErroValidacao e o valor de ponteiro nil. Apenas uma interface sem tipo e sem valor dinâmicos é igual a nil. Em funções que retornam error, retorne nil explicitamente no caminho de sucesso, como mostra o exemplo executável acima.

Veja também

Perguntas frequentes

O que é uma interface em Go?

Uma interface em Go define um conjunto de métodos. Um tipo implementa essa interface implicitamente quando seu method set contém todos os métodos exigidos, sem precisar declarar implements.

Go e Golang são linguagens diferentes?

Não. Go é o nome oficial da linguagem e Golang é o apelido popular usado em buscas. Interfaces em Go e interfaces em Golang são o mesmo recurso.

Qual é a diferença entre any e uma interface com métodos?

any é um alias de interface{} e aceita valores de qualquer tipo. Uma interface com métodos representa um comportamento específico e preserva um contrato verificável pelo compilador.

Quando usar interface em Go?

Use uma interface quando o código consumidor precisa aceitar mais de uma implementação do mesmo comportamento, especialmente em limites como io.Reader, armazenamento ou testes. Evite criar interfaces sem uma necessidade concreta.

Um valor e um ponteiro implementam a mesma interface em Go?

Nem sempre. Métodos com receiver de valor pertencem ao method set de T e de *T; métodos com receiver de ponteiro pertencem ao method set de *T, então apenas o ponteiro satisfaz uma interface que os exige.

Por que uma interface com ponteiro nil não é nil?

Uma interface só é nil quando não possui tipo dinâmico nem valor dinâmico. Ao receber um ponteiro nil de tipo *ErroValidacao, ela mantém esse tipo dinâmico e deixa de ser nil. Em funções que retornam error, retorne nil explicitamente quando não houver erro.