Bug: Value of module level global variable set from ENV does not work

Example code:

module example

global_const_variable::String = get(ENV, "GLOBAL_CONST", "hello_world")

function print_global_const_variable()
    global global_const_variable
    println(global_const_variable)
end

end

Example usage:

$ julia

julia> using example

julia> example.print_global_const_variable()
hello_world

(quit)

$ export GLOBAL_CONST='goodbye'
$ julia

julia> using example

julia> example.print_global_const_variable()
hello_world

julia> ENV["GLOBAL_CONST"]
"goodbye"

The exact behaviour appears to change depending on exactly which system this is run on. (eg: behavour on docker, Windows, may differ, etc)

This will only be set once, when the module is precompiled.

If you want something like this to run every time the module is loaded, you need to put it in the __init__ function.

7 Likes

Just to spell out the suggestion above:

module Example

global_const_variable::String = get(ENV, "GLOBAL_CONST", "hello_world")

function __init__()
    global global_const_variable = get(ENV, "GLOBAL_CONST", "hello_world")
end

function print_global_const_variable()
    global global_const_variable
    println(global_const_variable)
end

end

This is also applicable to initializing “const” data coming from external C calls. “const” is in quotes because the C pointer you get will change between sessions.

4 Likes

Is there no better solution than this? DRY

You don’t actually have to repeat yourself. Depending on your use case, you may want the environment variable to be set only during precompilation, or at every module load, but it doesn’t have to be both.
See this blog post for more details: Top level code in JuliaLang doesn't do what you think it does

1 Like

BTW - why isn’t this documented? This is pretty important information. I don’t recall reading about this anywhere in the Julia docs. Possibly I missed it?

It’s here:

You may need to be aware of certain behaviors inherent in the creation of incremental shared libraries which may require care when writing your module. For example, external state is not preserved. To accommodate this, explicitly separate any initialization steps that must occur at runtime from steps that can occur at compile time. For this purpose, Julia allows you to define an __init__() function in your module that executes any initialization steps that must occur at runtime.
Modules · The Julia Language

4 Likes

Thanks for the reference. I think this documentation page should be improved. The infomation is very dense, and this point is somewhat hidden near the bottom of the documentation page.

Since this is an important point it should really be near the beginning of the page, IMO.

If you think the documentation should be improved, you’re welcome to submit a pull request to start this conversation with language maintainers! Documentation PRs are a great way to start contributing (and then get nerdsniped into even more contributions ^^)

2 Likes

Environment variables are runtime concepts, and the way you handle them in other languages is not that different. In C/C++ libraries, an explicit initialization API is needed for getting environment variables. Python is more lenient and executes the module file sequentially during import. You can get such behavior in Julia with the following setting according to the manual:

If you know that a module is not safe to precompile (for example, for one of the reasons described below), you should put __precompile__(false) in the module file (typically placed at the top).

But even in Python, it’s considered poor practice to have side effects at import time, at least for large projects that need to be maintainable, and an initialization API is preferred. So I can hardly think of a situation when it’s worthwhile to use the __precompile__(false) setting in Julia (instead of writing a proper __init__ function).

This was mostly for backwards compatibility when precompilation was first introduced. Initially, __precompile__(false) was the default. Then __precompile__(true) became the default but they kept the __precompile__(false) to support older modules.

4 Likes