O que é Type em Go?
A palavra-chave type é um dos pilares do sistema de tipos de Go. Ela permite criar novos tipos nomeados, definir structs, declarar interfaces, criar aliases de tipos e estabelecer constraints para generics. O sistema de tipos de Go é fundamentalmente diferente de linguagens com herança de classes — em Go, tipos são compostos e combinados através de embedding e implementação implícita de interfaces.
Em Go, type não é apenas um mecanismo de nomenclatura. Criar um novo tipo com type gera um tipo distinto que não é intercambiável com o tipo original, mesmo que compartilhem a mesma representação em memória. Isso fornece segurança em tempo de compilação — o compilador impede misturas acidentais entre tipos semanticamente diferentes como Celsius e Fahrenheit, mesmo que ambos sejam float64 internamente.
Essa abordagem permite que Go ofereça tipagem forte com uma sintaxe leve, sem o peso de hierarquias de classe complexas. Combinado com interfaces implícitas e composição via embedding, o sistema de tipos de Go é expressivo o suficiente para modelar domínios complexos mantendo a simplicidade que define a linguagem.
Definição de tipo (type definition)
A forma mais comum de usar type é criar um novo tipo nomeado:
// Tipo baseado em tipo primitivo
type Celsius float64
type Fahrenheit float64
type UserID int64
type Email string
// O compilador impede misturas acidentais
var temp Celsius = 100
var fahr Fahrenheit = 212
// ERRO: cannot use temp (type Celsius) as type Fahrenheit
// fahr = temp // não compila!
// Conversão explícita necessária
fahr = Fahrenheit(temp*9/5 + 32) // OK
Métodos em tipos nomeados
Uma das vantagens de criar tipos nomeados é poder adicionar métodos a eles:
type Celsius float64
func (c Celsius) ParaFahrenheit() Fahrenheit {
return Fahrenheit(c*9/5 + 32)
}
func (c Celsius) String() string {
return fmt.Sprintf("%.1f°C", c)
}
temp := Celsius(100)
fmt.Println(temp) // 100.0°C
fmt.Println(temp.ParaFahrenheit()) // 212.0°F
Isso não é possível com tipos primitivos diretamente — você não pode adicionar métodos a float64. Criar um tipo nomeado resolve essa limitação e torna o código mais expressivo e seguro.
Tipos para domínio (Domain Types)
type Dinheiro int64 // centavos para evitar floating point
func (d Dinheiro) String() string {
reais := d / 100
centavos := d % 100
return fmt.Sprintf("R$ %d,%02d", reais, centavos)
}
func (d Dinheiro) Somar(outro Dinheiro) Dinheiro {
return d + outro
}
preco := Dinheiro(1999) // R$ 19,99
desconto := Dinheiro(500) // R$ 5,00
fmt.Println(preco.Somar(-desconto)) // R$ 14,99
Definição de struct
A aplicação mais frequente de type é definir structs:
type Pessoa struct {
Nome string
Email Email
Idade int
Ativo bool
CriadoEm time.Time
}
type Endereco struct {
Rua string
Cidade string
Estado string
CEP string
}
// Composição via embedding
type Cliente struct {
Pessoa // embedded — promove campos e métodos
Endereco // embedded
LimiteCredito Dinheiro
}
cli := Cliente{
Pessoa: Pessoa{Nome: "Ana", Email: "ana@email.com"},
Endereco: Endereco{Cidade: "São Paulo", Estado: "SP"},
LimiteCredito: Dinheiro(500000), // R$ 5.000,00
}
fmt.Println(cli.Nome) // "Ana" — campo promovido de Pessoa
fmt.Println(cli.Cidade) // "São Paulo" — campo promovido de Endereco
Definição de interface
Interfaces são definidas com type e especificam um conjunto de métodos:
type Leitor interface {
Ler(p []byte) (n int, err error)
}
type Escritor interface {
Escrever(p []byte) (n int, err error)
}
// Composição de interfaces
type LeitorEscritor interface {
Leitor
Escritor
}
// Interface com métodos múltiplos
type Repositorio interface {
Buscar(id int) (*Entidade, error)
Salvar(e *Entidade) error
Deletar(id int) error
Listar(filtro Filtro) ([]*Entidade, error)
}
Em Go, interfaces são implementadas implicitamente — não há implements keyword. Qualquer tipo que possua todos os métodos de uma interface automaticamente a satisfaz.
Type alias vs type definition
Go tem duas formas de criar tipos com type, e elas são fundamentalmente diferentes:
// TYPE DEFINITION — cria tipo NOVO e distinto
type MeuInt int
// TYPE ALIAS — cria nome alternativo para o MESMO tipo
type MeuAlias = int
Diferenças práticas
type NovoTipo int // tipo distinto
type Alias = int // mesmo tipo
var a NovoTipo = 10
var b int = 10
var c Alias = 10
// a = b // ERRO: tipos diferentes
a = NovoTipo(b) // OK: conversão explícita
// c = b // OK: são o mesmo tipo
// b = c // OK: são o mesmo tipo
// Métodos
func (n NovoTipo) Dobro() NovoTipo { return n * 2 } // OK
// func (a Alias) Dobro() Alias { ... } // ERRO: não pode adicionar método a tipo não-local
Quando usar alias
Type aliases são úteis em refatorações graduais e compatibilidade:
// Migração gradual — pacote antigo redireciona para novo
package antigo
import "projeto/novo"
// Alias mantém compatibilidade durante migração
type Config = novo.Config
type Server = novo.Server
Na biblioteca padrão, byte é um alias para uint8 e rune é alias para int32:
// Definidos na spec da linguagem
type byte = uint8
type rune = int32
Type assertion
Type assertion extrai o valor concreto de uma interface:
var i interface{} = "olá, Go!"
// Type assertion com panic se falhar
s := i.(string)
fmt.Println(s) // "olá, Go!"
// Type assertion segura com ok
n, ok := i.(int)
fmt.Println(n, ok) // 0, false — não é int
// Padrão idiomático
if s, ok := i.(string); ok {
fmt.Printf("É string: %q\n", s)
}
Type assertions são essenciais ao trabalhar com interfaces genéricas como interface{} (ou any no Go 1.18+) e ao implementar padrões como unmarshaling customizado:
type Validador interface {
Validar() error
}
func processar(dados interface{}) error {
// Verificar se implementa interface opcional
if v, ok := dados.(Validador); ok {
if err := v.Validar(); err != nil {
return fmt.Errorf("validação falhou: %w", err)
}
}
// processar dados...
return nil
}
Type switch
O type switch é uma extensão poderosa do select que permite branching baseado no tipo concreto:
func descrever(i interface{}) string {
switch v := i.(type) {
case int:
return fmt.Sprintf("inteiro: %d", v)
case string:
return fmt.Sprintf("string: %q (len=%d)", v, len(v))
case bool:
if v {
return "verdadeiro"
}
return "falso"
case []int:
return fmt.Sprintf("slice de %d inteiros", len(v))
case nil:
return "nil"
default:
return fmt.Sprintf("tipo desconhecido: %T", v)
}
}
Type switch com interfaces
type Forma interface {
Area() float64
}
type Circulo struct{ Raio float64 }
type Retangulo struct{ Largura, Altura float64 }
type Triangulo struct{ Base, Altura float64 }
func (c Circulo) Area() float64 { return math.Pi * c.Raio * c.Raio }
func (r Retangulo) Area() float64 { return r.Largura * r.Altura }
func (t Triangulo) Area() float64 { return t.Base * t.Altura / 2 }
func descreverForma(f Forma) string {
switch forma := f.(type) {
case Circulo:
return fmt.Sprintf("Círculo com raio %.1f", forma.Raio)
case Retangulo:
return fmt.Sprintf("Retângulo %g×%g", forma.Largura, forma.Altura)
case Triangulo:
return fmt.Sprintf("Triângulo base=%.1f", forma.Base)
default:
return "Forma desconhecida"
}
}
Type constraints (Generics — Go 1.18+)
Com a introdução de generics, type ganhou papel na definição de constraints:
// Constraint básica usando interface
type Numero interface {
~int | ~int8 | ~int16 | ~int32 | ~int64 |
~float32 | ~float64
}
func Somar[T Numero](valores []T) T {
var soma T
for _, v := range valores {
soma += v
}
return soma
}
// Funciona com qualquer tipo numérico
fmt.Println(Somar([]int{1, 2, 3, 4, 5})) // 15
fmt.Println(Somar([]float64{1.1, 2.2, 3.3})) // 6.6
O operador ~ (tilde)
O ~ em constraints significa “qualquer tipo cujo tipo subjacente seja”:
type Ordenavel interface {
~int | ~string | ~float64
}
// Funciona com tipos nomeados também
type Idade int
type Nome string
func Menor[T Ordenavel](a, b T) T {
if a < b {
return a
}
return b
}
var idade1, idade2 Idade = 25, 30
fmt.Println(Menor(idade1, idade2)) // 25 — funciona com tipo Idade
Constraints com métodos
type Stringifier interface {
String() string
}
type Comparable[T any] interface {
CompareTo(T) int
}
func MaxPor[T Comparable[T]](itens []T) T {
max := itens[0]
for _, item := range itens[1:] {
if item.CompareTo(max) > 0 {
max = item
}
}
return max
}
Underlying type (tipo subjacente)
Todo tipo em Go tem um tipo subjacente (underlying type). Para tipos predeclarados (int, string, etc.), o tipo subjacente é ele mesmo. Para tipos definidos com type, é o tipo na definição:
type MeuInt int // underlying: int
type MeuSlice []string // underlying: []string
type MeuMap map[string]int // underlying: map[string]int
// Conversão é possível entre tipos com mesmo underlying type
type Celsius float64
type Fahrenheit float64
c := Celsius(100)
f := Fahrenheit(c) // OK: ambos têm underlying float64
Esse conceito é crucial para entender quando conversões são possíveis e como constraints genéricos com ~ funcionam.
Type embedding (composição)
Go usa composição ao invés de herança. Type embedding é como Go “herda” comportamento:
type Animal struct {
Nome string
Idade int
}
func (a Animal) Apresentar() string {
return fmt.Sprintf("Sou %s, tenho %d anos", a.Nome, a.Idade)
}
type Cachorro struct {
Animal // embedded — promove campos e métodos
Raca string
}
rex := Cachorro{
Animal: Animal{Nome: "Rex", Idade: 5},
Raca: "Labrador",
}
// Campos e métodos promovidos
fmt.Println(rex.Nome) // "Rex" — de Animal
fmt.Println(rex.Apresentar()) // "Sou Rex, tenho 5 anos"
fmt.Println(rex.Raca) // "Labrador" — próprio
Embedding funciona com interfaces também, permitindo composição de contratos:
type Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}
type ReadWriter interface {
Reader
Writer
}
Padrões avançados com type
Enum pattern
Go não tem enums nativos, mas tipos nomeados com constantes simulam o padrão:
type Status int
const (
StatusPendente Status = iota
StatusAtivo
StatusInativo
StatusBloqueado
)
func (s Status) String() string {
nomes := [...]string{"Pendente", "Ativo", "Inativo", "Bloqueado"}
if int(s) < len(nomes) {
return nomes[s]
}
return "Desconhecido"
}
func (s Status) EhValido() bool {
return s >= StatusPendente && s <= StatusBloqueado
}
Functional options com type
type Opcao func(*Servidor)
type Servidor struct {
host string
port int
timeout time.Duration
}
func ComTimeout(d time.Duration) Opcao {
return func(s *Servidor) {
s.timeout = d
}
}
func ComPorta(port int) Opcao {
return func(s *Servidor) {
s.port = port
}
}
func NovoServidor(host string, opts ...Opcao) *Servidor {
s := &Servidor{host: host, port: 8080, timeout: 30 * time.Second}
for _, opt := range opts {
opt(s)
}
return s
}
Tipo para validação
type CPF string
func NovoCPF(valor string) (CPF, error) {
limpo := strings.Map(func(r rune) rune {
if r >= '0' && r <= '9' {
return r
}
return -1
}, valor)
if len(limpo) != 11 {
return "", fmt.Errorf("CPF inválido: deve ter 11 dígitos")
}
// ... validação de dígitos verificadores
return CPF(limpo), nil
}
func (c CPF) Formatado() string {
s := string(c)
return fmt.Sprintf("%s.%s.%s-%s", s[:3], s[3:6], s[6:9], s[9:])
}
Boas práticas com type
- Crie tipos de domínio para valores semanticamente distintos (UserID, Email, Dinheiro)
- Use type assertion com forma segura (comma-ok) para evitar panics
- Prefira type switch sobre múltiplas type assertions encadeadas
- Use composição (embedding) ao invés de simular herança
- Defina constraints mínimas em generics — peça apenas o necessário
- Mantenha interfaces pequenas — 1-3 métodos é o ideal em Go
- Documente o zero value dos tipos que você cria
Para aprofundar, explore como types se integram com interfaces para polimorfismo, com structs para modelagem de dados e com generics para código reutilizável.
Perguntas frequentes (FAQ)
Qual a diferença entre type definition e type alias em Go?
Uma type definition (type NovoTipo int) cria um tipo completamente novo e distinto — não é intercambiável com o tipo original sem conversão explícita e pode ter métodos próprios. Um type alias (type Alias = int) cria apenas um nome alternativo para o mesmo tipo — é totalmente intercambiável e não pode ter métodos adicionais. Use definitions para segurança de tipo e aliases para migrações graduais.
Como funciona type assertion em Go?
Type assertion (valor.(Tipo)) extrai o valor concreto de uma interface. Use a forma segura v, ok := i.(Tipo) que retorna false em ok se o tipo não corresponder, evitando panic. Type assertion só funciona em valores de interface — não em tipos concretos. Para múltiplas verificações de tipo, prefira type switch.
Quando usar generics vs interface em Go?
Use interfaces quando precisa de polimorfismo em runtime (diferentes implementações trocadas dinamicamente) e generics (type constraints) quando precisa de polimorfismo em compile-time com performance máxima (sem boxing/unboxing). Regra prática: se o comportamento varia, use interface. Se apenas o tipo de dado varia mas a lógica é idêntica, use generics.
Posso adicionar métodos a tipos de outros pacotes?
Não diretamente. Go só permite adicionar métodos a tipos definidos no mesmo package. Para “estender” tipos externos, crie um novo tipo baseado nele: type MeuSlice []string e adicione métodos a MeuSlice. Isso é proposital — evita modificações imprevisíveis de tipos de terceiros e mantém o código Go previsível e fácil de entender.