Racket 8.0: Avoiding Accidental Public Exposure via provide (all-defined-out)
0 reputation · 08 Nov 2023, 22:20 UTC
0 reputation · 08 Nov 2023, 22:20 UTC
The goal is to maintain strict encapsulation of helper functions when migrating from Racket 7.0 to 8.0. In 7.0, (only-in mod id) allowed importing any top‑level binding, regardless of whether it was exported. 8.0 tightened this rule so that only identifiers explicitly exported by provide can be imported. Developers often add (provide (all-defined-out)) as a quick fix, but this unintentionally promotes internal helpers to the public API, increasing the risk of name collisions and misuse by downstream users.
There is no compile‑time warning in 8.10 when a module re‑exports an identifier that was not originally exported, leaving the decision entirely to the programmer. This lack of feedback may lead to fragile code that becomes incompatible with future releases that further tighten module semantics.
What mechanisms, if any, should Racket provide to alert authors when re‑exporting non‑originally exported identifiers? How can developers balance the convenience of (only-in) with the need to keep helper functions private without exposing them through (provide (all-defined-out))?
31775 reputation · 09 Nov 2023, 08:47 UTC
Racket 8.0 does not emit a compile‑time warning when a module re‑exports an identifier that was not originally exported. The safest approach is to stop using (provide (all-defined-out)) in production libraries and replace it with an explicit export list or a small helper module that keeps internal helpers truly private.
(only-in mod id) could import any top‑level binding, even if it was never provided.only-in may only reference identifiers that mod has explicitly exported.(provide (all-defined-out)) lifts all top‑level definitions into the public API, exposing helpers that might change or be removed later.(provide foo bar baz)
;; helpers.rkt
(define (helper-a x) ...)
(define (helper-b y) ...)
;; main.rkt
(require "helpers.rkt")
(provide foo bar) ; only re‑export the public API
helpers.rkt only if they truly need the internals.racket-lint or racket-verify to scan for (provide (all-defined-out)) in your codebase.(provide (all-defined-out)) appears in a library module.only-in UsesAfter upgrading to 8.0, run a quick grep to find any (only-in that references a module that no longer provides the imported identifier. Example:
grep -R "(only-in" -n . | grep -v "provide" | grep -v "#lang"
Any remaining matches indicate imports that will now fail. Update them to use the new exported names or remove the import.
If you still rely on (provide (all-defined-out)) in a library that is consumed by other projects, I’d like to know:
(only-in) to import non‑exported identifiers?Answering these questions will help tailor a migration plan that preserves your API contract while keeping internals hidden.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.