why import cycle not allowed?

5,388 views
Skip to first unread message

Jiacheng Guo

unread,
Apr 16, 2014, 12:40:57 PM4/16/14
to golan...@googlegroups.com
hi,
   I'm wondering why import cycle not allowed in Go. It seems to me forbid import cycle make package init() function call a bit easier to reason but it should not be a too hard issue to solve. A common use case of cyclic import for me is to declare a common interface in top level package, then make a few implementations in different sub-package, finally make some convenient function like a factory to forward specific implementation in top level package. However, forbid cyclic dependence make this process very difficult and fragile, especially when there is multiple different interface involved. Why this design choice is taken?

Best Regards,
Jiacheng Guo

Robert Johnstone

unread,
Apr 16, 2014, 12:59:34 PM4/16/14
to golan...@googlegroups.com
Hello,

Put your common interface into one package, you implementation in their respective packages, and then use a new package for the factory.  No cyclic dependencies.

Alternatively, have your sub-packages register with init.  Look at the database/sql or image packages for example.

egon

unread,
Apr 16, 2014, 2:54:50 PM4/16/14
to golan...@googlegroups.com


On Wednesday, April 16, 2014 7:40:57 PM UTC+3, Jiacheng Guo wrote:
hi,
   I'm wondering why import cycle not allowed in Go. It seems to me forbid import cycle make package init() function call a bit easier to reason but it should not be a too hard issue to solve. A common use case of cyclic import for me is to declare a common interface in top level package, then make a few implementations in different sub-package, finally make some convenient function like a factory to forward specific implementation in top level package. However, forbid cyclic dependence make this process very difficult and fragile, especially when there is multiple different interface involved. Why this design choice is taken?

Cyclic dependencies introduce problematic "init" order. Avoiding cyclic dependencies enforces simpler packages that have clearer dependencies and it's easier to reason about effects of changes in packages. Cyclic dependencies allows much more easily to reason about what needs to be compiled when something changes.


Of course there have been discussions on the forums as well.

+ egon
 
Best Regards,
Jiacheng Guo

John Asmuth

unread,
Apr 16, 2014, 9:37:25 PM4/16/14
to golan...@googlegroups.com
The 'factory' function you describe does not have to be in the original package.

The very pattern you describe is something I have with go.wde.

github.com/skelterjohn/go.wde is the high-level interface.
github.com/skelterjohn/go.wde/windows is the windows-specific implementation.
github.com/skelterjohn/go.wde/xgb is the X windows-specific implementation.
github.com/skelterjohn/go.wde/init is a convenience package that will use the right backend for your current system.

In go, a package is a compilation unit. If two files must always be compiled together, they must be in the same package.

Jiacheng Guo

unread,
Apr 17, 2014, 2:32:13 AM4/17/14
to golan...@googlegroups.com
I'm using this pattern. However, I feel create a separate can be very fragile and hard to maintain when there is multiple related interface to implement, as they may have to gain an instance from factory,  and fragment the package too much.  

"Alternatively, have your sub-packages register with init.  Look at the database/sql or image packages for example."

This seems to be an interesting approach. I will check it out

Thanks,
Jiacheng Guo

Brendan Tracey

unread,
Apr 17, 2014, 2:56:59 AM4/17/14
to golan...@googlegroups.com
One of the big reasons is compile times. If two packages import each other, when one changes they must both be recompiled. Same goes for all cycles. In the worst case, your whole program must be recompiled with every change.

In my mind, the lack of cyclical dependencies is one of go's best decisions for supporting programming at scale. You are a good programmer, so you want to keep your packages small for legibility (and godoc helps highlight the complexity). Go forbids cyclical dependencies, and so your program is a DAG consisting of a bunch of small chunks. This combination goes a long way in preventing spaghetti code even as programs get large.

egon

unread,
Apr 17, 2014, 3:09:06 AM4/17/14
to golan...@googlegroups.com


On Thursday, April 17, 2014 9:32:13 AM UTC+3, Jiacheng Guo wrote:
I'm using this pattern. However, I feel create a separate can be very fragile and hard to maintain when there is multiple related interface to implement, as they may have to gain an instance from factory,  and fragment the package too much.  


Remember that you can declare the interfaces in multiple places without any problem.
 
"Alternatively, have your sub-packages register with init.  Look at the database/sql or image packages for example."


Instead of "init" approach you can always make a separate package that pulls the things together. i.e. manually.


How to resolve the cyclic dependency depends on what you are implementing.

+ egon

Jiacheng Guo

unread,
Apr 17, 2014, 3:20:25 AM4/17/14
to golan...@googlegroups.com
"Remember that you can declare the interfaces in multiple places without any problem"
This is indeed a very good insight for me. I  got too used  to old oo approach, that declare an interface once then everyone try to implement it. Declare an interface multiple time can solve some case for me.

Jan Mercl

unread,
Apr 17, 2014, 3:41:45 AM4/17/14
to Jiacheng Guo, golang-nuts
On Wed, Apr 16, 2014 at 6:40 PM, Jiacheng Guo <guo...@gmail.com> wrote:
> Why this design choice is taken?

It's not really a choice, there's only one option when initialization
of something depends on something else being already initialized,
which is what the specs guarantee[0]. Without such guarantee, everyone
would have to handle correct initialization code explicitly - in code.

IOW, in the general case there's a partial ordering of initialized
things. Correct initialization order is the depth first traversal of
the dependency tree(s). Allowing cycles would mean there's no correct
initialization order available. Except for some rather trivial (no
dependencies present) cases - the dependency "tree(s)" form a list.

(The problem is also related to determining sizes of types, but that's
not the topic here.)

[0]: http://golang.org/ref/spec#Program_execution

-j

chris dollin

unread,
Apr 17, 2014, 7:37:37 AM4/17/14
to Jiacheng Guo, golang-nuts
On 16 April 2014 17:40, Jiacheng Guo <guo...@gmail.com> wrote:
 
A common use case of cyclic import for me is to declare a common interface in top level package, then make a few implementations in different sub-package,

Aside: Go-the-language doesn't have any notion of "sub"-package.
They're just packages.

Chris

--
Chris "allusive" Dollin

Ingo Oeser

unread,
Apr 17, 2014, 12:07:03 PM4/17/14
to golan...@googlegroups.com


On Thursday, April 17, 2014 9:20:25 AM UTC+2, Jiacheng Guo wrote:
"Remember that you can declare the interfaces in multiple places without any problem"
This is indeed a very good insight for me. I  got too used  to old oo approach, that declare an interface once then everyone try to implement it. Declare an interface multiple time can solve some case for me.



Actually it is more useful to declare the minimal interface very close to the consumer expecting the interface.
That way you can keep both updated very easily. It is also completely opposite to the traditional approach of 
declaring an interface close to a random bunch of entities implementing that interface.
Reply all
Reply to author
Forward
0 new messages