Sharing a package global dictionary with other packages

I want to define a dictionary in one package that other client packages can access and update. For a proof of concept, let’s define a “base” module Foo and a client module Bar:

# module `Foo` defines a global dictionary and methods to add or recall it:

module Foo

const DICT = Dict()

println("resetting DICT.")
dict() = DICT

function add(item)
    println("adding $item")
    DICT[item] = string(item)
end

add(:foo_item)

end

# module `Bar` imports `Foo` and adds to the dictionary

module Bar
import ..Foo: add, dict
export dict, add

add(:bar_item)

end

Then I get the following expected behaviour:

using .Bar

dict()
# Dict{Any, Any} with 2 entries:
#   :foo_item => "foo_item"
#   :bar_item => "bar_item"

add(:main_item)
# adding main_item
# "main_item"

dict()
# Dict{Any, Any} with 3 entries:
#   :foo_item  => "foo_item"
#   :main_item => "main_item"
# :bar_item  => "bar_item"

However, if I turn Foo and Bar into packages, then new behaviour emerges:

julia> using Bar
[ Info: Precompiling Bar [84147f7a-2164-45cb-9498-7c2329c08b93]
resetting DICT.
adding foo_item
adding bar_item

dict()
# Dict{Any, Any} with 1 entry:
#   :foo_item => "foo_item"

Where has the :bar_item gone? It seems the dictionary has been reset at some point after Bar adds to it, but I never see that reflected in the logging I’ve put in.

What do I need to add to my mental model to explain why packages don’t behave like modules? And, what’s the right way to do something like this?

I am not using Revise.jl here.

2 Likes

You probably want to read the docstring for __init__ and/or Modules · The Julia Language.

1 Like

You have hit the issues (or bless?) of precompilation.

Julia packages are precompiled independently. Global variables are deserialized from precompile cache per package, so modifying them does not work.

Sharing global variables and modifying their contents is not do-able in Julia. In short, just initialize and modify your collections in Julia module’s __init__ methods. These __init__ methods correspond to Python files’ toplevel statements.


########################
## Foo.jl package

module Foo

const DICT = Dict()

dict() = DICT


// ...

function __init__()
    # runtime statements here
    empty!(DICT)
end
end # module

########################
## Bar.jl package

module Bar
  export dict
  import Foo: add, dict

   function __init__()
      # runtime statements here
      add(:foo_item)
   end
end # end module


########################
## Main

using Bar

dict()

# Dict{Any, Any} with 2 entries:
#   :foo_item => "foo_item"
#   :bar_item => "bar_item"

P.S: If you feel strange, you’d better get to know the point that “the time of executing Julia modules’ toplevel statements could correspond to the time of expanding C++ templates”. You shouldn’t expect static variables created by C++ templates can be modified in other compilation units.

5 Likes

Thank you @GunnarFarneback and @thautwarm. I understand now.

1 Like