Interfaces Avançadas em Go 2026: Composição, Asserções de Tipo e Perguntas de Entrevista

Dominar a composição de interfaces Go, asserções de tipo e genéricos autorreferenciais do Go 1.26. Inclui exemplos práticos e perguntas de entrevista técnica.

Interfaces Avançadas em Go 2026

As interfaces em Go definem comportamento sem prescrever implementação, tornando-as centrais para escrever código flexível e testável. Go 1.26 estende esse poder com genéricos autorreferenciais, novos iteradores de reflexão e tratamento de erros type-safe via errors.AsType. Este guia cobre composição de interfaces, asserções de tipo, padrões de embedding e as perguntas que surgem em entrevistas técnicas.

Melhoria de Interfaces no Go 1.26

Tipos genéricos agora podem se referenciar em sua lista de parâmetros de tipo: type Adder[A Adder[A]] interface { Add(A) A }. Este padrão de polimorfismo F-bounded permite interfaces que restringem tipos de retorno ao próprio tipo implementador.

Composição de Interfaces e Padrões de Embedding

O embedding de interfaces combina múltiplas interfaces em um único contrato. O resultado é uma nova interface que requer todos os métodos de suas interfaces incorporadas. Este padrão evita duplicação e cria limites de abstração claros.

interfaces.gogo
type Reader interface {
    Read(p []byte) (n int, err error)
}

type Writer interface {
    Write(p []byte) (n int, err error)
}

type Closer interface {
    Close() error
}

// Composed interface embedding three interfaces
type ReadWriteCloser interface {
    Reader
    Writer
    Closer
}

Qualquer tipo que implemente os métodos Read, Write e Close automaticamente satisfaz ReadWriteCloser. A biblioteca padrão do Go usa este padrão extensivamente no pacote io.

Uma pergunta comum de entrevista pede aos candidatos que expliquem por que Go prefere interfaces pequenas e focadas. A resposta está na composabilidade: io.Reader aparece em centenas de funções porque exige exatamente um método. Interfaces maiores criam acoplamento mais forte e reduzem a reutilização.

Asserções de Tipo: Sintaxe e Segurança

Asserções de tipo extraem um tipo concreto de um valor de interface. A forma de dois valores previne panics retornando um booleano indicando sucesso.

assertions.gogo
func processValue(v interface{}) {
    // Two-value assertion: safe, no panic
    if str, ok := v.(string); ok {
        fmt.Printf("String value: %s\n", str)
        return
    }
    
    // Type switch for multiple types
    switch val := v.(type) {
    case int:
        fmt.Printf("Integer: %d\n", val)
    case float64:
        fmt.Printf("Float: %.2f\n", val)
    case []byte:
        fmt.Printf("Bytes: %x\n", val)
    default:
        fmt.Printf("Unknown type: %T\n", val)
    }
}

Type switches lidam com múltiplos tipos possíveis de forma limpa. Cada caso vincula val ao tipo afirmado dentro de seu bloco, eliminando a necessidade de asserções separadas.

Risco de Panic

Asserções de valor único como str := v.(string) causam panic se a asserção falhar. Código em produção deve sempre usar a forma de dois valores ou um type switch.

Os entrevistadores frequentemente perguntam sobre o desempenho das asserções de tipo. O runtime realiza uma única comparação de descritores de tipo, tornando as asserções baratas. O custo aumenta com valores de interface que envolvem ponteiros para structs grandes devido à indireção, mas a asserção em si permanece O(1).

Genéricos Autorreferenciais no Go 1.26

Go 1.26 introduziu parâmetros de tipo autorreferenciais, permitindo interfaces onde métodos devem retornar o tipo implementador. Este padrão, às vezes chamado de polimorfismo F-bounded, resolve uma limitação de longa data.

builder.gogo
// Self-referential interface: methods return the same type
type Builder[B Builder[B]] interface {
    WithName(name string) B
    WithAge(age int) B
    Build() string
}

type PersonBuilder struct {
    name string
    age  int
}

func (p PersonBuilder) WithName(name string) PersonBuilder {
    p.name = name
    return p
}

func (p PersonBuilder) WithAge(age int) PersonBuilder {
    p.age = age
    return p
}

func (p PersonBuilder) Build() string {
    return fmt.Sprintf("%s, %d years old", p.name, p.age)
}

// Generic function using the self-referential constraint
func configure[B Builder[B]](b B, name string, age int) string {
    return b.WithName(name).WithAge(age).Build()
}

A restrição Builder[B] garante que WithName e WithAge retornem B, não um Builder genérico. Sem isso, o tipo de retorno seria a interface, perdendo a informação do tipo concreto e quebrando o encadeamento de métodos.

Tipos matemáticos se beneficiam deste padrão. Uma interface Addable[A Addable[A]] garante que Add(A) A retorne o mesmo tipo numérico, prevenindo a mistura acidental de valores BigInt e Decimal.

errors.AsType: Unwrapping de Erros Type-Safe

Go 1.26 adicionou errors.AsType, substituindo o padrão de dança de ponteiros de errors.As. A versão genérica retorna o erro unwrapped diretamente.

errors_handling.gogo
type ValidationError struct {
    Field   string
    Message string
}

func (e *ValidationError) Error() string {
    return fmt.Sprintf("validation failed on %s: %s", e.Field, e.Message)
}

func handleError(err error) {
    // Go 1.26: generic type-safe unwrapping
    if valErr, ok := errors.AsType[*ValidationError](err); ok {
        log.Printf("Validation error on field %s\n", valErr.Field)
        return
    }
    
    // Before Go 1.26: pointer-based unwrapping
    // var valErr *ValidationError
    // if errors.As(err, &valErr) { ... }
    
    log.Printf("Unexpected error: %v\n", err)
}

A nova API elimina a declaração de variável separada e torna o tipo alvo explícito na chamada de função. As cadeias de erros são pesquisadas da mesma forma que antes; apenas a interface mudou.

Pronto para mandar bem nas entrevistas de Go?

Pratique com nossos simuladores interativos, flashcards e testes tecnicos.

Iteradores de Reflexão para Inspeção de Interfaces

Go 1.26 adicionou métodos iteradores ao pacote reflect. Type.Methods() e Value.Methods() retornam iteradores para percorrer métodos, substituindo loops baseados em índice.

reflection.gogo
import "reflect"

func inspectInterface(v interface{}) {
    t := reflect.TypeOf(v)
    
    // Go 1.26: iterator-based method inspection
    fmt.Printf("Type %s methods:\n", t.Name())
    for method := range t.Methods() {
        fmt.Printf("  %s: %s\n", method.Name, method.Type)
    }
    
    // For struct fields (also new in 1.26)
    if t.Kind() == reflect.Struct {
        for field := range t.Fields() {
            fmt.Printf("  Field: %s (%s)\n", field.Name, field.Type)
        }
    }
}

O padrão de iterador se alinha com o recurso range-over-function do Go 1.23. O código se torna mais legível, e não há penalidade de desempenho: iteradores produzem valores de forma lazy.

Satisfação de Interface em Tempo de Compilação

Go verifica a satisfação de interface em tempo de compilação ao atribuir um valor concreto a uma variável de interface. Verificações explícitas usando atribuições com identificador em branco detectam erros cedo em bases de código grandes.

compile_check.gogo
type Storage interface {
    Save(key string, data []byte) error
    Load(key string) ([]byte, error)
    Delete(key string) error
}

type FileStorage struct {
    basePath string
}

// Compile-time check: fails if FileStorage misses a method
var _ Storage = (*FileStorage)(nil)

func (f *FileStorage) Save(key string, data []byte) error {
    path := filepath.Join(f.basePath, key)
    return os.WriteFile(path, data, 0644)
}

func (f *FileStorage) Load(key string) ([]byte, error) {
    path := filepath.Join(f.basePath, key)
    return os.ReadFile(path)
}

func (f *FileStorage) Delete(key string) error {
    return os.Remove(filepath.Join(f.basePath, key))
}

A linha var _ Storage = (*FileStorage)(nil) compila apenas se *FileStorage satisfizer Storage. Este padrão detecta métodos faltantes imediatamente em vez de em runtime quando um valor é atribuído.

A Interface Vazia e Restrições de Tipo

A interface vazia interface{} aceita qualquer valor, enquanto any é seu alias desde Go 1.18. Restrições genéricas fornecem segurança de tipo em tempo de compilação sem asserções em runtime.

constraints.gogo
import "golang.org/x/exp/constraints"

// Generic function with numeric constraint
func Sum[T constraints.Integer | constraints.Float](values []T) T {
    var total T
    for _, v := range values {
        total += v
    }
    return total
}

// Comparable constraint for map keys
func Contains[K comparable, V any](m map[K]V, key K) bool {
    _, exists := m[key]
    return exists
}

Preferir genéricos sobre interface{} elimina asserções de tipo e detecta incompatibilidades de tipo em tempo de compilação. A compensação é complexidade adicional nas assinaturas de função, então genéricos se encaixam melhor quando uma função genuinamente opera sobre múltiplos tipos.

Perguntas de Entrevista Técnica sobre Interfaces Go

Os entrevistadores testam conhecimento de interfaces em múltiplos níveis. Aqui estão perguntas comuns com respostas concisas.

P: O que acontece quando se chama um método em um valor de interface nil versus um valor concreto nil dentro de uma interface?

Uma interface nil não tem tipo nem valor; chamar qualquer método causa panic. Uma interface não-nil contendo um ponteiro nil tem um tipo; o método executa com um receiver nil. Este comportamento permite padrões como (*bytes.Buffer)(nil).String() retornando uma string vazia.

P: Como funciona a comparação de interfaces?

Dois valores de interface são iguais se tiverem o mesmo tipo dinâmico e valores dinâmicos iguais. Comparar interfaces com tipos não comparáveis (slices, maps, funções) causa panic em runtime.

P: Quando um método deve usar um receiver de ponteiro versus um receiver de valor?

Receivers de ponteiro permitem mutação e evitam copiar structs grandes. Receivers de valor são seguros para uso concorrente e funcionam tanto com valores quanto com ponteiros. Se algum método precisa de um receiver de ponteiro, todos os métodos nesse tipo devem usar receivers de ponteiro por consistência, já que apenas *T satisfaz uma interface que requer um método com receiver de ponteiro.

receiver_rules.gogo
type Counter struct {
    count int
}

// Pointer receiver: modifies the struct
func (c *Counter) Increment() {
    c.count++
}

// Value receiver: read-only operation
func (c Counter) Value() int {
    return c.count
}

type Incrementer interface {
    Increment()
}

func main() {
    var c Counter
    // var i Incrementer = c  // Compile error: Counter lacks Increment
    var i Incrementer = &c     // OK: *Counter has Increment
    i.Increment()
}

P: Explicar o princípio "aceitar interfaces, retornar structs".

Funções que aceitam interfaces se desacoplam de implementações concretas, permitindo testes com mocks. Retornar tipos concretos dá aos chamadores acesso completo aos métodos do tipo sem asserções de tipo. Retornar uma interface esconde futuras adições de métodos atrás de uma asserção de tipo.

Para mais preparação para entrevistas Go, veja o módulo de perguntas sobre concorrência Go e o módulo de testing.

Dica de Entrevista

Quando pedido para projetar uma interface, começar com o caso de método único. Expandir apenas quando múltiplos métodos são sempre chamados juntos. As interfaces Stringer, Reader e Handler da biblioteca padrão definem cada uma um único método.

O que Desenvolvedores Go Sênior Devem Saber sobre Interfaces

  • A composição de interfaces através de embedding cria contratos flexíveis sem hierarquias de herança. Manter interfaces pequenas: um a três métodos cobre a maioria dos casos de uso.
  • Asserções de tipo e type switches extraem tipos concretos com segurança. Sempre usar a forma de dois valores ou um switch em código de produção para evitar panics.
  • Os genéricos autorreferenciais do Go 1.26 permitem padrões builder e tipos matemáticos onde métodos devem retornar o tipo implementador.
  • errors.AsType simplifica o unwrapping de erros com uma API genérica e type-safe. Usar em código novo direcionado ao Go 1.26 ou posterior.
  • Verificações de interface em tempo de compilação com var _ Interface = (*Type)(nil) detectam métodos faltantes antes dos testes executarem.
  • Preferir genéricos sobre interface{} quando a segurança de tipo em tempo de compilação supera a complexidade nas assinaturas.
  • A diretriz "aceitar interfaces, retornar structs" mantém APIs flexíveis para chamadores enquanto expõe funcionalidade completa.

Comece a praticar!

Teste seus conhecimentos com nossos simuladores de entrevista e testes tecnicos.

Desafio do dia

Você saberia encontrar o bug em Go?

Um trecho real, um bug escondido, uma tentativa por dia. Sem conta para testar.

Anthony Fillion-Maillet

Escrito por

Anthony Fillion-Maillet

Fundador da SharpSkill

Desenvolvedor fullstack há mais de 10 anos. Dirige a SharpSkill e responde por tudo o que é publicado aqui.

Atualizado em 10 de setembro de 2026

Tags

#go
#interfaces
#type-assertions
#generics
#go-1.26

Compartilhar

Artigos relacionados