Erlang Module Export vs. Private Functions: Avoiding Accidental Public Access
0 reputation · 13 Apr 2023, 21:09 UTC
0 reputation · 13 Apr 2023, 21:09 UTC
Goal: Keep helper predicates hidden while still enabling quick prototyping with the -export_all compiler flag.
private attribute; non‑exported functions rely on convention.-export_all option overrides any explicit export list, making accidental exposure likely.Whether the language should introduce a formal private attribute or similar mechanism to prevent accidental export and aid static analysis.
private attribute to existing modules?-export_all is used?32775 reputation · 14 Apr 2023, 05:33 UTC
To keep helper predicates truly private while still enjoying -export_all for quick experiments, never ship code compiled with -export_all. Use explicit -export lists in production, and constrain -export_all to interactive sessions or temporary build directories. Combine this with a naming convention (e.g., prefix helpers with _) and a lint rule that flags any exported function matching that prefix when -export_all is active.
-export are inaccessible from other modules at runtime; the rule is enforced by the BEAM VM, not just a convention.+export_all (or {export_all,true} in rebar.config) overrides any explicit -export list, making every function public.-export but no matching clause exists, the compiler emits a warning: warning: function X/Y is not defined. This does not stop beam generation.xref can surface unintended exports: Dialyzer reports functions that are exported but never called externally, and xref can be invoked with the exports flag for a similar scan.-export_all in production builds. CI pipelines can enforce explicit export lists via compiler flags or lint plugins.Because Erlang allows dynamic calls (apply/3, spawn/3 with a module/fun/arity tuple), the language designers opted for a simple visibility model: a function is either exported or not. Adding a formal -private attribute would break backward compatibility—existing modules that rely on -export_all would suddenly hide functions, breaking callers that depend on the current all‑public behavior. Therefore, visibility control remains at the module‑attribute level.
-export([Public/Arity, ...]) and omit helper functions.-export_all to prototyping. Use it only in an interactive shell (erl -compile(export_all)) or a temporary build directory. Do not commit or ship beam files compiled with this flag._helper/1). This makes accidental export obvious.rebar3_lint or elvis that:
-export_all in production profiles, and
_ when -export_all is active.dialyzer --src -r src/ and look for messages such as Function ... is exported but never used externally to surface over‑exposed functions.# 1. Confirm warning for undefined export
cat > test.erl <<EOF
-module(test). -export([foo/0, bar/0]). foo() -> ok.
EOF
erlc -W test.erl
# Expected: warning: function bar/0 is not defined
# 2. Verify -export_all makes an undefined function callable
cat > test2.erl <<EOF
-module(test2). -export([foo/0]). foo() -> ok.
EOF
erlc +export_all test2.erl
erl -noshell -eval 'test2:foo().' -s init stop
# foo/0 works; if bar/0 existed it would be callable too
# 3. Run Dialyzer to detect over‑exported helpers
# (Assumes a project in src/)
dialyzer --src -r src/ 2>&1 | grep "exported but never used externally"
# 4. Example rebar3_lint rule to forbid -export_all in prod
# In rebar.config:
# {rebar3_lint, [
# {profiles, [prod]},
# {rules, [
# {forbid_export_all, []},
# {exported_private_prefix, ["_"]}
# ]}
# ]}.
Which build tool and OTP version are you using (e.g., rebar3 3.22, OTP 26.2)? This determines which lint plugins and compiler options are available and whether xref integration is straightforward.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.