Error with modules sharing types

The following code gives me an error with Julia v1.4

# M1.jl
module M1
    export M1Type
    struct M1Type
        data::Real
    end
end

# M2.jl
module M2
    using ..M1
    export f
    function f(x::M1Type)
        
    end
end

# At the REPL (or main module)
using .M1
using .M2
f(M1Type(1))

and the error is

 MethodError: no method matching f(::M1Type) Closest candidates are: f(::Main.M1.M1Type) Stacktrace:..

Currently the workaround I use is to have the main module only have using .M1, and use Reexport.jl to reexport everything in M1 from M2, but I shouldn’t need to do that, right?

Pasting exactly the code you provided into the REPL works as expected:

julia> # M1.jl
       module M1
           export M1Type
           struct M1Type
               data::Real
           end
       end
Main.M1

julia> # M2.jl
       module M2
           using ..M1
           export f
           function f(x::M1Type)
               
           end
       end
Main.M2

julia> # At the REPL (or main module)
       using .M1

julia> using .M2

julia> f(M1Type(1))

julia> 

so the problem has something to do with how you are actually including your files. The results you’re seeing are consistent with having called include() on the same file in multiple different modules, which you not ever do (see Organizing modules. Is it OK to organize a project using several modules in Julia? , Organizing structs and modules , etc). Can you be more explicit about how you’ve actually set up your code to run into the problem you’re seeing?

2 Likes

Huh I just tried it again and it worked fine. Weird, sorry about that.

The results you’re seeing are consistent with having called include() on the same file in multiple different modules, which you not ever do

Yes, that’s exactly what I was doing. More precisely, this replicates the error:

(each section in its own file this time)

# module_1.jl
module M1
    export M1Type
    struct M1Type
        data::Real
    end
end
# module_2.jl
module M2
    include("module_1.jl")
    using .M1
    export f
    function f(x::M1Type)
        
    end
end
#main.jl
include("module_1.jl")
include("module_2.jl")
using .M1
using .M2
f(M1Type(1))

If I don’t use the include statements in this way, the compiler complains about not being able to recognize certain functions/types. From what I’ve read, I could organize them into their own projects, and import them by modifying LOAD_PATH, but is that the only way to have local modules? It seems unnecessarily complex.

Yeah, you definitely don’t want to do that–you are creating two completely unrelated modules called M1 by calling include() twice. If you’re familiar with C++, this would be like including the same header outside of and inside of a namespace.

You don’t need to use projects for this (although projects are incredibly useful). Just add the current directory (".") to your LOAD_PATH and get rid of all your include() calls:

julia> push!(LOAD_PATH, ".")
4-element Array{String,1}:
 "@"
 "@v#.#"
 "@stdlib"
 "."

julia> using M1
[ Info: Precompiling M1 [top-level]
1 Like

As a side note: the definition

struct MyType
    data::Real
end

is generally discouraged in favor of

struct MyType{T<:Real}
    data::T
end

(https://docs.julialang.org/en/v1/manual/performance-tips/#Avoid-fields-with-abstract-type-1)
As far as I know, there is no reason at all to declare abstract types for immutable struct fields: since an immutable struct’s data fields cannot be changed, binding the value to a concrete type at construction incurs no restriction. There might only be a reason to do that for mutable types, where you might indeed need to change the data in object during its lifetime.

2 Likes