Javascript's Module pattern for encapsulation in Julia

I have done some JavaScript recently and I discovered the module pattern. Something like this


const moduleSecret = (() => {
    let secret = "I'm hidden !";
    return {
        getSecret: () => secret,
        setSecret: (s) => { secret = s; }
    };
})();
console.log(moduleSecret.getSecret()); // "I'm hidden !"

So I was asking myself, if I really need to hide something in my package, can’t I use the same pattern?


function MyModule ()
    secret="I'm hidden !"

    return (getSecret = _ -> secret, setSecret = s -> (secret = s))

end

This way, we can keep sensible data private

This is called closures

I have never found closures useful, because the variables in a closure can be converted into constants within the scope of a module.

module MyModule
const secret = "I'm hidden !"
getSecret() = secret
setSecret(s) = (secret = s)
export getSecret, setSecret
end

In addition, for zero-argument functions, you can use getSecret = () -> secret instead of getSecret = _ -> secret

1 Like

One common idiom here is not through a module, but through let scopes:

julia> let
           global get_secret, set_secret!
           secret = "I'm hidden"
           get_secret() = secret
           set_secret!(v) = (secret = v)
       end
set_secret! (generic function with 1 method)

julia> get_secret()
"I'm hidden"

julia> set_secret!("I'm still hidden")
"I'm still hidden"

julia> secret
ERROR: UndefVarError: `secret` not defined

If you use a Julia module here, you’ll be able to directly access MyModule.secret.

5 Likes

I see, I learned a new way from your reply. To keep the secret variable completely hidden, I can’t use module constants. Thank you!

For a long time I also thought that this would work, until I saw post where someone showed that the secret can be accessed in the following undocumented way:

getset = MyModule()
println(getset.getsecret.secret)

(since this is undocumented behaviour, it will not necessarily occur in future Julia versions)

1 Like

I mean, there will always exist, somewhere in your program, the string "I'm hidden !" along with a way to access and modify that binding. If someone can run arbitrary code within that program, they will be able to reproduce whatever Julia itself does, either through your “official” functions or whatever internals (documented or not) Julia itself uses to effectuate the change. The only question is how hard can you make it.

2 Likes

This mean nothing can totally be private in Julia.
But this pattern is still pretty effective, since it can be used in a regular module to keep some data hidden, I doubt someone will put that much effort to access a variable

setSecret doesn’t do anything here, it just creates a new local secret and throws it away immediately. global secret = s would fail in v1.11- because it’s const. v1.12+ allows const redefinitions (with reasonable limits on its influence), but not like that.

julia> MyModule.setSecret(3)
3

julia> MyModule.secret
"I'm hidden !"

But it’s worth pointing out that the global get_secret, set_secret! methods in a let block aren’t treated as closures and don’t get the named fields that a closure would; the local secret is boxed separately and the method just references it namelessly. The same effect can be flipped with local in begin:

julia> begin # in global scope
           local secret = "I'm hidden"
           get_secret() = secret
                             # borrows outer local
           set_secret!(v) = (secret = v)
       end;

julia> @code_typed set_secret!(3)
CodeInfo(
1 ─     Core.setfield!(Core.Box("I'm hidden"), :contents, v)::Int64
└──     return v
) => Int64

julia> set_secret!.secret
ERROR: type #set_secret! has no field secret

I actually don’t know of a way to access that box, I imagine it’ll take some pointer magic instead of a few base functions.

2 Likes

Some variant of that will always be true. Julia is not a sandboxed language. There are no security boundaries within julia: That is the operating system’s job, leveraging hardware capabilities.

The same is true in modern java: security manager / applets are dead, because they were a neverending wellspring of vulns.

The same is mostly true in node.js.

The only exception is javascript (and webassembly) in web-browsers. Browsers are effectively operating systems, and it is essential that they can run malicious code with bounded permissions (i.e. shady websites’ JS). And this is a great feat of engineering, effectively the only software-defined sandbox that works! (you could argue about ebpf, but I’m not running ebpf code from shady websites in my kernel)

So you should absolutely forget about using language features for “secrets”. The entire concept and question is misguided.

Instead, properly document that this is an internal thing that your module’s users shouldn’t access, on pain of breakage in the next update.

You as a package author are not in control – ideally the person with physical access to the machine running the code is in control, and your job is to make their lives easier. They can access the field. If it’s stupid to access the field, warn them, but don’t make futile attempts to prevent it. They can anyways pull the plug, use freeze-spray on the RAM and then read the field, shitting all over the tower of abstractions in your mind.

4 Likes