As it turns out, the recursive type definitions aren’t actually relevant to the problems you’re having.
First off, this function:
isn’t working for you because it’s not being called at all. Try this:
julia> methods(Foo)
# 2 methods for type constructor:
[1] (::Type{Foo})(x::T, bar::B) where {T, B<:Bar} in Main at REPL[3]:2
[2] (::Type{Foo})(x, bar) in Main at REPL[10]:1
Method [1] is the constructor, which matches any x and and bar::Bar. Method [2] is the function you wrote, which matches any x and any bar. So method [2] is less specific and is therefore never actually being called. That’s why it has no effect.
If you make that function more specific (e.g. by making it Foo(x, bar::Bar), then you’re going to run into more problems because on the very next line you do foo = Foo(x, bar). If this function were working at all, it would always just call itself immediately, leading to a stack overflow.
This is a classic case where an inner constructor solves the problem:
julia> mutable struct Foo{T, B <: Bar}
x::T
bar::B
function Foo(x::T, bar::B) where {T, B <: Bar}
foo = new{T, B}(x, bar)
bar.foo = foo
return foo
end
end
julia> foo = Foo(10, bar)
Foo{Int64,Bar{Float64}}(10, Bar{Float64}(2.0, Foo{Int64,Bar{Float64}}(#= circular reference @-2 =#)))
julia> foo.bar.y
2.0
This isn’t about the incomplete initialization–try it on a simpler case:
julia> struct Baz{T}
x::T
Baz(x) = new(x)
end
ERROR: syntax: too few type parameters specified in "new{...}" around REPL[17]:1
You have to tell new what T should be, otherwise how does it know what type to construct?
julia> struct Baz{T}
x::T
Baz(x::T) where {T} = new{T}(x)
end
By the way, you can also make the foo field of Bar concrete in your example using an extra type parameter (although it starts to get a bit mind-bending). Here’s a simplified example of two types which contain each other with no type instabilities:
julia> struct Bar{F}
foo::F
end
julia> mutable struct Foo
bar::Bar{Foo}
function Foo()
foo = new()
foo.bar = Bar(foo)
return foo
end
end
julia> foo = Foo()
Foo(Bar{Foo}(Foo(#= circular reference @-2 =#)))
julia> foo.bar
Bar{Foo}(Foo(Bar{Foo}(#= circular reference @-2 =#)))
julia> foo.bar.foo === foo
true