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:
- Desacoplamento real: o tipo não precisa “saber” sobre a interface
- Interfaces podem ser definidas pelo consumidor: quem usa o código define a interface, não quem implementa
- Retroatividade: tipos existentes satisfazem interfaces criadas depois
- 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 tipo | T satisfaz? | *T satisfaz? |
|---|---|---|
| Todos com value receiver | Sim | Sim |
| Algum com pointer receiver | Nao | Sim |
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ção | Tipo dinâmico | Valor dinâmico | Interface == nil? |
|---|---|---|---|
var err error | Ausente | Ausente | Sim |
var p *ErroValidacao; var err error = p | *ErroValidacao | Ponteiro nil | Não |
var err error = &ErroValidacao{} | *ErroValidacao | Ponteiro não nulo | Nã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étodoio.Writer: 1 métodoio.Closer: 1 métodofmt.Stringer: 1 métodoerror: 1 métodosort.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
anycom 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
- Testes em Go — Use interfaces para criar mocks
- Tratamento de Erros — A interface error
- Microservices com Go — Interfaces em arquitetura limpa
- Go para Iniciantes — Fundamentos de Go