Circular package dependency: it works but is it OK?

I have a scenario where there are three modules which have simple dependencies but their packages have circular dependencies (it's caused by one of their test modules. More on this later). The project compiles. I wonder if it's OK to keep using it this way?

More details. The three modules are utils module, custom protocol module, and macros module.

  • Utils is the basic module. It doesn't depend on other modules.
  • Custom protocol module uses the types defined in utils module, so it depends on utils module.
  • Macros module have extension macro which expand extensions for protocols defined in custom protocol module, so it depends on custom protocol module.

The dependency is simple, as illustrated in the diagram.

screenshot-2023-12-10_20-26-47

Ideally their package should have the same dependency. However, as I use macro to implement tests for Utils, Utils package depends on macros package. That caused circular dependency among these three packages.

As Xcode compiles my project successfully, I didn't realize there was circular dependency at first. After I found it, I think I can get rid of the circular dependency by split macros package into two packages, but I'm not sure if it's worth the effort since circular dependency works fine in practice. Does anyone know if it's by design or by accident? Thanks.

UPDATE: it's weird. I believe my project have had circular package dependency for quite a while and it always worked fine. But shortly after I posted the question, I found one package failed to compile with the following error:

cyclic dependency declaration found: Utils -> Macro -> EntityProtocol -> Utils

I'm still able to compile the other two packages, although they are in the circular dependency too.

I think I should split macro package. I'll leave the post in case anyone has comments or suggestions.

Classic schrödinbug

schroedinbug, n.

[MIT: from the Schroedinger's Cat thought-experiment in quantum physics]

A design or implementation bug in a program that doesn't manifest until someone reading source or using the program in an unusual way notices that it never should have worked, at which point the program promptly stops working for everybody until fixed. Though (like bit rot) this sounds impossible, it happens; some programs have harbored latent schroedinbugs for years.

3 Likes