Initialise a dict in a mutually circular type

I need to define two mutually circular types. I have read this issue, which suggests using a forward declaration. That works. However, I also need to contain the types in Dicts, which must be initialised.

abstract type AbstractB end

mutable struct A
  b::AbstractB
  A() = new()
end

mutable struct B <: AbstractB
  a::A
  adict::Dict{String, A}
  B() = new()
end

a = A()
b = B()

# This works
a.b = b
b.a = a

# But this gives an error, because the dictionary wasn't initialised.
b.adict["1"] = a
# ERROR: UndefRefError: access to undefined reference

This time, I use an inner constructor to initialise the Dict to an empty dictionary. It seems to recursively call the circular types, even though the dictionary is empty.

# NEW SESSION =========================

# This time, initialize the dictionary with an empty dictionary.

abstract type AbstractB end

mutable struct A
  b::AbstractB
  stringdict::Dict{String, AbstractB}
  A() = new(B(), Dict{String, B}())
end

mutable struct B <: AbstractB
  a::A
  adict::Dict{String, A}
  B() = new(A(), Dict{String, A}())
end

a = A() # StackOverflowError.  Why, given that the dict has zero entries?

Is there a way around this? I’d really like to use the types, because these structures will be used a lot.

Because your constructor for A() calls B(), which is the inner constructor for B, which calls A(), the inner constructor for which calls B(),…

2 Likes

Thanks. I’d like to not initialise a or b, but that doesn’t seem to be possible. I tried an inner constructor, but it had the original UndefRefError problem :

function B()
  self = new()
  self.adict = Dict{String, A}()
  self
end

One possible workaround is to make B.a a union of A, Nothing and have the default constructor set a to nothing.

Also, this might help.

3 Likes

That works. Also I just realised you’re the author of LightGraphs, which inspired me to try Julia. Thank you on both counts!

I had read that link before, and it lost me at “As with incomplete objects returned from constructors, if complete_me or any of its callees try to access the data field of the Lazy object before it has been initialized, an error will be thrown immediately.” It seems to recommend delegating completion to a function that isn’t allowed to access the incomplete data field.

2 Likes

Thanks very much for this. I hope it’s working for you.

I wouldn’t worry too much about that part. Basically, it just means this:

julia> mutable struct A
       x::String
       A() = new()
       end

julia> z = A()
A(#undef)

julia> z.x
ERROR: UndefRefError: access to undefined reference
Stacktrace:
 [1] getproperty(::A, ::Symbol) at ./Base.jl:33

julia> z.x = "hello, world!"
"hello, world!"

julia> z
A("hello, world!")

(Note that if x is an Integer or some other primitive (?) type, it will receive an invalid, as opposed to undefined, value:

julia> mutable struct A                                                                                                                               
       x::Int                                                                                                                                         
       A() = new()
       end

julia> z = A()
A(140069649368400)

)

2 Likes