Mutating global variable during precompilation

Hi, I am trying to register items in a top-level global variable from include()d files and modules, and am seeing behavior I am not able to explain. Here is a minimal code:

File main.jl:

module MyPkg

items = []

function register(item)
    push!(items, item)
end

include("sub.jl")
println(items)

end

File sub.jl:

module Sub

using MyPkg: register

register("foo")

end

When running my script with julia main.jl I get the output:

Any["foo"]
Any[]

In my understanding, the first output is made during precompilation, and the second at runtime. Unfortunately the registered items are lost at runtime. What is going on exactly?

To be more concrete about my use case, I am trying to build a CLI with sub-commands registering themselves with the top-level command. The list of registered commands is fixed before precompilation and does not need to change at runtime. I would like to avoid having the main module gather all the commands itself, and rather having a nice and simple interface to delegate to sub-modules and let them register their commands themselves. How could I achieve this?

Thanks !

Actually, running this without any modification to LOAD_PATH or the current package project results in:

ERROR: LoadError: LoadError: ArgumentError: Package MyPkg not found in current path:
- Run `import Pkg; Pkg.add("MyPkg")` to install the MyPkg package.

because the using MyPkg: register tries to load some global package with that name, but none exists. The right way to refer to unambiguously refer to a parent module like this is with the ..MyPkg syntax, e.g.:

module Sub

using ..MyPkg: register

register("foo")

end

Making that change and running julia main.jl prints:

$ julia main.jl
Any["foo"]

By the way, you should change items = [] to const items = []. The const label just means you will not change the binding items to some other value or type, but it does not stop you from mutating the underlying vector. The const marking will just improve performance of any code using that variable (per https://docs.julialang.org/en/v1/manual/performance-tips/#Avoid-global-variables ).

5 Likes

Thanks @rdeits! I indeed observe the same behavior as you when making the change to using ..MyPkg.

I am actually using a package project, running my program with julia --project src/main.jl. Trying different things, I observe two different behaviors:

  • With using MyPkg: register (and a package project), I get the original two-lines output, with an empty array the second time
  • With using ..MyPkg: register (with or without a package project), I get the same output as yours

Can you tell me what the difference is and what happens behind the scene?

My understanding is that using MyPkg:register will actually use MyPkg from the active Project.toml file from the deps section. While using ..MyPkg will use the MyPkg defined in the current running “environment” i.e. from a Julia file loaded via the include or the main Julia file.

So the MyPkg in these two situations might not be the same module. You should be able to test this by doing ] followed by rm MyPkg that will remove the package from the Project.toml dependency list then using MyPkg should result in a package not found.