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

  1. Planeje a arquitetura de pacotes — defina camadas claras (domain, service, handler, repository). Veja clean architecture em Go.

  2. Dependa de abstrações — use interfaces para desacoplar pacotes.

  3. Siga o princípio da dependência — pacotes de nível alto não devem importar pacotes de nível baixo diretamente.

  4. Pacotes pequenos e coesos — cada pacote deve ter uma responsabilidade clara. Consulte Go Modules na Prática.

  5. 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