How to let two modules use part of each other?


#1

For instance:

module moduleA

using moduleB

export StructA

mutable struct StructA 
	i::Int
	SB::StructB
end

end
module moduleB

# import moduleA.StructA
using moduleA: StructA

export StructB
export func

mutable struct StructB
	i::Int
end

function func(A::StructA)
	A.i += 1
end

end

The above does not work:

julia> using moduleA
[ Info: Precompiling moduleA [top-level]
β”Œ Warning: Module moduleA with build ID 89941935020118 is missing from the cache.
β”‚ This may mean moduleA [top-level] does not support precompilation but is imported by a module that does.
β”” @ Base loading.jl:944
ERROR: LoadError: LoadError: UndefVarError: StructA not defined
Stacktrace:
 [1] include at ./boot.jl:317 [inlined]
 [2] include_relative(::Module, ::String) at ./loading.jl:1041
 [3] _require(::Base.PkgId) at ./loading.jl:953
 [4] require(::Base.PkgId) at ./loading.jl:855
 [5] macro expansion at ./logging.jl:311 [inlined]
 [6] require(::Module, ::Symbol) at ./loading.jl:837
 [7] include at ./boot.jl:317 [inlined]
 [8] include_relative(::Module, ::String) at ./loading.jl:1041
 [9] include(::Module, ::String) at ./sysimg.jl:29
 [10] top-level scope at none:2
 [11] eval at ./boot.jl:319 [inlined]
 [12] eval(::Expr) at ./client.jl:389
 [13] top-level scope at ./none:3
in expression starting at moduleB.jl:5
in expression starting at moduleA.jl:4
ERROR: Failed to precompile moduleA [top-level] to moduleA.ji.
Stacktrace:
 [1] error(::String) at ./error.jl:33
 [2] macro expansion at ./logging.jl:313 [inlined]
 [3] compilecache(::Base.PkgId, ::String) at ./loading.jl:1187
 [4] macro expansion at ./logging.jl:311 [inlined]
 [5] _require(::Base.PkgId) at ./loading.jl:944
 [6] require(::Base.PkgId) at ./loading.jl:855
 [7] macro expansion at ./logging.jl:311 [inlined]
 [8] require(::Module, ::Symbol) at ./loading.jl:837

#2

I suggest you do this instead:

module ModuleB
export StructB

mutable struct StructB
	i::Int
end
end


module ModuleA
export StructA, func
using ..ModuleB: StructB

mutable struct StructA
	i::Int
	SB::StructB
end

function func(A::StructA)
    A.i += 1
end
end

using .ModuleA, .ModuleB
func(StructA(1, StructB(1)))

or if func needs to belong to ModuleB then this

module ModuleB
export StructB, func

mutable struct StructB
	i::Int
end
function func end
end


module ModuleA
export StructA
using ..ModuleB: StructB
import ..ModuleB: func

mutable struct StructA
	i::Int
	SB::StructB
end

function func(A::StructA)
    A.i += 1
end
end

using .ModuleA, .ModuleB
func(StructA(1, StructB(1)))

If you really, really need to do it in your original way then this works:

module ModuleB
export StructB

mutable struct StructB
	i::Int
end
end


module ModuleA
export StructA
using ..ModuleB: StructB

mutable struct StructA
	i::Int
	SB::StructB
end
end

ModuleB.@eval begin
    using ..ModuleA: StructA
    export func
    function func(A::StructA)
	A.i += 1
    end
    nothing
end

using .ModuleA, .ModuleB
func(StructA(1, StructB(1)))

but that code sure is smelly.

PS: convention has it to name modules camel case, but with upper first letter. Have a read through https://docs.julialang.org/en/v1/manual/style-guide/.


#3

One other technique I’ve seen for this issue: if type A needs to depend on type B that isn’t defined yet, then instead you can make A depend on a type parameter T, and later use A{B} once B is defined.