Accidental Public Access When Defining Vars in Auto‑Imported Clojure Namespaces
0 reputation · 30 Dec 2020, 00:00 UTC
0 reputation · 30 Dec 2020, 00:00 UTC
Clojure vars are package‑private by default, but when a var is defined in a namespace that is automatically loaded—such as via :require [clojure.tools.namespace.repl :as nrepl]—the var can be accessed from the REPL's current namespace, leading to accidental public exposure.
The goal is to guarantee that a var remains inaccessible outside its defining namespace, even if the defining namespace is auto‑imported, without relying on naming conventions or manual metadata.
Clojure provides no built‑in :private metadata beyond ^:public, and future changes to namespace loading or tooling could alter visibility, making static analysis insufficient for detecting accidental exposure.
How can a developer enforce true privacy for vars defined in auto‑imported namespaces without external tools? Does Clojure's metadata system support a :private flag that persists across namespace reloads?
35775 reputation · 30 Dec 2020, 03:52 UTC
Clojure's default scoping—package-private for namespace-defined variables—is the mechanism for enforcing privacy, not metadata. Auto-importing a namespace, such as via :require, makes the namespace available in the REPL's context but does not elevate the visibility of its internal, package-private variables.
The perceived accidental public access stems from the REPL operating within a context that has loaded the namespace, not from a change in the variable's intrinsic scope. There is no built-in :private metadata flag that persists across namespace reloads to override this default behavior.
This adherence to default package-private scoping is the only reliable, language-level guarantee against accidental exposure, irrespective of how the namespace is loaded.
Use comments to ask for clarification. Post a solution as an answer.
35,775 reputation · 30 Dec 2020, 01:15 UTC
The `^:private` metadata flag on vars exists in Clojure 1.11 and later. On those releases, `def ^:private x 1` stores the flag on the var, and it survives namespace reloads even when the namespace is auto‑imported. Older versions lack this flag, so default package‑private scoping remains the only guarantee.
Verify before relying on it: run `clojure -V`, define the var, reload the namespace, then inspect `(meta #'x)` and attempt `(find-var 'other-ns/x)` from outside the defining namespace.