import cycle not allowed em Go
O erro “import cycle not allowed” acontece quando dois ou mais pacotes dependem um do outro, criando um ciclo de importação. O compilador Go proíbe rigorosamente dependências circulares — se o pacote A importa o pacote B e o pacote B importa o pacote A (diretamente ou por meio de outros pacotes), seu código não compila.
Essa restrição é uma decisão de design que garante compilação rápida e uma arquitetura de dependências clara. Resolver ciclos de importação geralmente resulta em código melhor estruturado.
A Mensagem de Erro
package meuapp/handlers
imports meuapp/models
imports meuapp/handlers: import cycle not allowed
Ou de forma mais indireta:
package meuapp/a
imports meuapp/b
imports meuapp/c
imports meuapp/a: import cycle not allowed
Causas Comuns
1. Dependência Direta Bidirecional
O caso mais óbvio — dois pacotes que dependem um do outro:
// pacote: models/user.go
package models
import "meuapp/handlers" // Importa handlers
type User struct {
Nome string
}
func (u *User) Handler() handlers.UserHandler {
return handlers.UserHandler{User: u}
}
// pacote: handlers/user_handler.go
package handlers
import "meuapp/models" // Importa models — CICLO!
type UserHandler struct {
User *models.User
}
2. Ciclo Indireto (Três ou Mais Pacotes)
Mais difícil de detectar — o ciclo passa por vários pacotes:
auth → users → notifications → auth (CICLO!)
3. Testes Importando Pacote Pai
Pacotes de teste _test que importam indiretamente seu próprio pacote:
// pacote: utils/helpers.go
package utils
import "meuapp/service"
func FormatService(s *service.Service) string { ... }
// pacote: service/service.go
package service
import "meuapp/utils" // CICLO se utils importa service
Como Resolver
Solução 1: Extrair Interfaces para Pacote Separado
Crie um pacote de “contratos” (interfaces) que outros pacotes implementam:
// pacote: domain/interfaces.go
package domain
// Interface que define o contrato
type UserRepository interface {
FindByID(id int) (*User, error)
Save(user *User) error
}
type User struct {
ID int
Nome string
}
// pacote: handlers/user_handler.go
package handlers
import "meuapp/domain" // Depende de interfaces, não de implementação
type UserHandler struct {
repo domain.UserRepository
}
func NewUserHandler(repo domain.UserRepository) *UserHandler {
return &UserHandler{repo: repo}
}
// pacote: repository/postgres.go
package repository
import "meuapp/domain" // Depende do mesmo pacote de interfaces
type PostgresUserRepo struct{}
func (r *PostgresUserRepo) FindByID(id int) (*domain.User, error) {
// implementação
return &domain.User{ID: id, Nome: "Alice"}, nil
}
func (r *PostgresUserRepo) Save(user *domain.User) error {
return nil
}
Esse padrão é a base da clean architecture em Go. As interfaces permitem desacoplar pacotes sem sacrificar a segurança de tipos.
Solução 2: Mover Tipos Compartilhados para Pacote Comum
Se dois pacotes compartilham tipos, extraia-os para um terceiro pacote:
ANTES (ciclo):
handlers ←→ models
DEPOIS (sem ciclo):
domain ← handlers
domain ← models
// pacote: domain/types.go
package domain
type User struct {
ID int
Nome string
}
type CreateUserRequest struct {
Nome string
Email string
}
Solução 3: Inversão de Dependência
Ao invés de A depender de B e B depender de A, faça ambos dependerem de uma abstração:
// pacote: notifier/notifier.go
package notifier
// Interface — não depende de ninguém
type UserProvider interface {
GetEmail(userID int) (string, error)
}
type EmailNotifier struct {
users UserProvider
}
func New(users UserProvider) *EmailNotifier {
return &EmailNotifier{users: users}
}
func (n *EmailNotifier) NotifyUser(userID int, msg string) error {
email, err := n.users.GetEmail(userID)
if err != nil {
return err
}
// enviar email...
_ = email
_ = msg
return nil
}
Solução 4: Usar Callbacks ou Funções como Parâmetro
Passe comportamento como função ao invés de importar o pacote:
// pacote: processor/processor.go
package processor
// Aceita uma função ao invés de importar o pacote de log
type LogFunc func(msg string)
func Process(data []byte, logFn LogFunc) error {
logFn("processando dados...")
// processar...
logFn("concluído")
return nil
}
Solução 5: Unificar Pacotes Pequenos
Se dois pacotes são tão acoplados que dependem mutuamente um do outro, talvez eles devessem ser um único pacote:
ANTES (ciclo):
user_model ←→ user_validator
DEPOIS (unificado):
user (contém model + validator)
Ferramentas para Detectar Ciclos
go list
Visualize as dependências de um pacote:
# Lista imports diretos
go list -f '{{.Imports}}' ./handlers/
# Lista todas as dependências (transitivas)
go list -f '{{.Deps}}' ./handlers/
Diagrama de Dependências
Use ferramentas como godepgraph para visualizar:
go install github.com/kisielk/godepgraph@latest
godepgraph ./... | dot -Tpng -o deps.png
Dicas para Evitar Ciclos de Importação
Planeje a arquitetura de pacotes — defina camadas claras (domain, service, handler, repository). Veja clean architecture em Go.
Dependa de abstrações — use interfaces para desacoplar pacotes.
Siga o princípio da dependência — pacotes de nível alto não devem importar pacotes de nível baixo diretamente.
Pacotes pequenos e coesos — cada pacote deve ter uma responsabilidade clara. Consulte Go Modules na Prática.
Teste com
go build ./...— compile todos os pacotes regularmente para detectar ciclos cedo. Integre ao CI/CD.
Outras linguagens resolvem dependências circulares de formas diferentes: Rust também proíbe ciclos entre crates, incentivando a mesma separação de responsabilidades que Go exige.
Erros Relacionados
- undefined: variable or function — pode surgir quando você reestrutura pacotes para resolver ciclos
- Go Modules na Prática — organizando dependências corretamente
- Clean Architecture em Go — padrões de arquitetura que evitam ciclos
- Microserviços em Go — quando dividir em serviços ao invés de pacotes