Are circular references problematic?

I have this structure in my code:

abstract type SystemComponent end

mutable struct System
    components::Dict{String, <:SystemComponent}

    function System()
        new(Dict{String, Component}())
    end
end

mutable struct Component<: SystemComponent
    label::AbstractString
    parent::System
    components::Dict{String, <:SystemComponent}

    function Component(label::AbstractString, parent::System)
        self = new(label, parent, Dict{String, SystemComponent}())
        parent.components[label] = self
        return self
    end
end

sys = System()
comp1 = Component("comp1", sys)

When I print sys it get:
System(Dict{String, Component}("comp1" => Component("comp1", System(#= circular reference @-3 =#), Dict{String, SystemComponent}())))

Is the #= circular reference @-3 =# something I should worry about? My quick research indicated that circular reference should be avioded. However, is that the case here? What would be an alternative approach?

What you see in the output (#= circular reference @-3 =#) is not an issue itself. It is just a way to show instances with circular references.

If Julia did not care about circular references when showing instances, you would get stuck in a loop (show system โ†’ show component โ†’ show system โ†’ โ€ฆ). This is essentially the reason why circular references are a bad idea. When the code that your using does not account for circular references, you can get errors (see, e.g., StackOverflow in hash for circular references ยท Issue #46725 ยท JuliaLang/julia ยท GitHub).

An alternative approach depends on the problem. Do you really need to have references in both directions (system โ†’ components, component โ†’ system)? For example, could you always start from System and go down to the components?

function some_function(system::System)
    for comp_name, comp in system.components
        # now "parent" == system
        do_something(system, comp)
    end
end
2 Likes

Thank for the answer!

The idea is to inherit parameters from parent components. I have multiple layers of components and if a required parameter is not defined, the next place to look for is in the parent component. Addionally, I need to know which children each component has. So I kind a need both directions. However, I think it would be possible to handle these relations on the root level (=System) and avoid self references.

Or use circular references and keep that in mind when writing code :slight_smile: It does not have to be wrong; sometimes it is necessary (think doubly linked lists).

1 Like