Package Development: Why is Ref needed to pass a global constant to a function?

I currently have the following setup (which works great) but somehow seems inelegant:

src/MyPackage.jl

module MyPackage
include("mysrc.jl")
const CONFIG = Ref{Config}()
end

src/mysrc.jl

@with_kw struct Config
    a :: Int64 = 0
    [...]
end

function myfunc()
    @unpack_Config CONFIG[]
    [...]
end

scripts/myscript.jl

const CONFIG[] = Config();
res = myfunc()

What exactly am I telling the compiler? Is there a reason for no keyword like container const to accomplish the same thing as Ref{} and the brackets?

I think many packages do something like


module M

struct Config
    ...
end
const config = Ref{Config}()

f(arg1) = f(CONFIG[], arg1)
function f(config::Config, arg1)
    #logic with config and arg
end
end

That way, you can bypass the default config when you need to without changing it for all calls.

It’s also worth noting that you could use ScopedValues introduced in 1.11 to make dynamic but const-global like variables instead of large config structs if you’re always unpacking anyway.

1 Like

Not sure why a Ref would be necessary for this pattern to work though?

In a slightly different setting, Preferences.jl might be useful too.

2 Likes

I think the Ref pattern originates in a time where Julia didn’t have typed globals. Nowadays, typed globals should behave (almost?) identical to that pattern.

2 Likes

Ref also doesn’t need to be initialized at parsing time too. But I agree that typed globals are fine in general!

Maybe its time I just put this on github, but for now I’ll continue with the pseudo-package. Based on the comments I tried:

module MyPackage
include("mysrc.jl")
# const CONFIG = Ref{Config}()
end
@with_kw struct Config
    a :: Int64 = 0
    [...]
end

function myfunc()
    @unpack_Config CONFIG
    [...]
end
const CONFIG = Config();
res = myfunc()

I get the error:

julia> res = myfunc()
nested task error: UndefVarError: `CONFIG` not defined in `MyPackage`

If I instead do:

module MyPackage
include("mysrc.jl")
const CONFIG :: Config = Config()
end

Then the values cannot be modified in a script:

julia> CONFIG.a = 10
ERROR: setfield!: immutable struct of type Config cannot be changed

julia> CONFIG = Config(a = 10)
WARNING: redefinition of constant Main.CONFIG. This may fail, cause incorrect answers, or produce other errors.

Can anyone clarify what you mean?

This seems to work though. In the package module (no const):

CONFIG :: Config = Config()

Then declare it const in the script:

const CONFIG = Config(a = 10);
res = myfunc()

I’ll have to check its actually using that new value, but that seems much cleaner.

EDIT:
It doesn’t work. The original value assigned in the module is still used.

It really seems like a weird pattern. Can’t you use something like Preferences.jl?

Maybe, the main problem is I’m porting a C++ program that reads in a few text files for configuration. I’m trying to keep it backwards compatible.

I can also foresee a use for running multiple julia sessions with different config values at the same time.

But I definitely need to look into Preferences.jl.

Can you explain/link what those colons are doing there? I haven’t seen that before.

Just a typo. I fixed it now. Originally I was writing it out as a typed global!

1 Like

Ref allows you to change configuration.

struct Config
    v::Bool
end
const config = Ref{Config}(Config(true))

f(c::Config, x::Bool) = c.v == x
f() = f(config[], true)

@assert f()                 # with original configuration
config[] = Config(false)    # change configuration
@assert !f()                # with changed configuration
1 Like

Thanks, it felt like I was using a workaround “trick” though.

I was wondering about why there isn’t a more elegant way (or more likely I missed it).

Preferences.jl may be a solution but even that requires an entire new file. I’m sure theres some history in github comments I don’t know how to find.

@mrufsvold pointed out ScopedValues in Julia 1.11. Maybe that’s what you’re looking for? I think it was around as its own package pre 1.11.

1 Like