RFA-590 · Case file with fixtures · Case 562 of 694 · Compiler evidence
Rust Private Methods Need a Public Capability Boundary
A public type does not make every operation public. Expose the smallest capability that preserves the module's invariant and future implementation freedom.
- Reviewed
- Rust
- Rust 1.98.1, edition 2024
- Targets
- all Rust targets
- Profiles
- dev, release, test
Direct answer
What this Rust failure means
- Why it happens
- Reachability of the type and constructor was assumed to grant access to a separate method whose visibility remains module-private.
- First discriminating check
- Decide what capability external callers need, then expose a policy-preserving wrapper or mark the method public only as an intentional API promise.
A public type can expose selected operations while keeping other methods inside its owning module. The failing fixture can construct Token, but its call to private rotate crosses the module boundary and produces E0624.
Visibility belongs to each item
pub struct Token makes the type reachable. pub fn new makes construction reachable. Neither declaration changes the default privacy of another method. This per-item control lets a module publish a useful abstraction without publishing its maintenance machinery.
The official E0624 explanation offers two directions: keep use within the allowed scope through a public wrapper, or make the method public. I treat this as an API decision rather than adding pub automatically.
For token rotation, public access might require authentication state, persistence, revocation, and audit events. A raw internal method could assume those steps already happened. Exposing it directly may compile while permitting invalid workflows.
The fixture repair demonstrates intentional exposure
The repaired fixture marks rotate public. Its body has no invariant, so direct exposure is safe for the isolated diagnostic. In production I would probably expose a higher-level operation accepting the necessary context and returning a meaningful result.
A module-level function can also call the private method after enforcing policy. This keeps the low-level operation private while giving callers one reviewed capability. Rust's module privacy makes that pattern straightforward.
I use pub(crate) when all application modules may call an operation but downstream crates should not. pub(super) or pub(in path) can narrow collaboration further. The visibility reference defines which ancestor paths are valid.
Private methods are refactoring freedom
Downstream code cannot rely on a private method's name, arguments, error type, or timing. I can merge it into another operation, split it, or change representation without a public compatibility promise.
Making a method public should therefore include documentation, stability review, and tests from the caller's perspective. It can be easier to expose data than behaviour now, but that choice becomes expensive once external users depend on it.
For traits, visibility works differently: trait items follow the trait's accessible contract rather than receiving individual pub qualifiers. An inherent helper and a trait method may share a concept but have different public obligations.
Tests and generated code can encounter this boundary
Unit tests inside the defining module can call private methods; integration tests compile as external crates and cannot. I prefer integration tests to exercise the public contract. If a crucial state is observable only through private internals, I add a safe diagnostic view or assert through behaviour rather than widening everything for tests.
Macros expand with hygiene and privacy rules tied to where items are resolved. A derive or generated client failing with E0624 may need an officially supported public hook, not a fragile visibility workaround. I inspect expansion and generator expectations.
If a dependency made a formerly public method private, I read its release guidance. The replacement may intentionally enforce a stronger workflow. Recreating access with unsafe layout tricks or duplicate logic discards that design.
Capability naming should describe effects
A public method named rotate should say whether it mutates only memory, persists state, invalidates old tokens, or performs network I/O. Error and idempotency behaviour matter to callers. Privacy gives me the chance to build this stable semantic boundary around internal pieces.
I often expose query methods more freely than mutation methods, but read operations can also leak secrets or create timing and locking costs. Visibility review includes security and performance, not only mutation.
My E0624 checklist
- Which module owns the method and where is the call made?
- Is the method private by design or accidentally missing a visibility modifier?
- What invariant or workflow does the internal method assume?
- Can a public wrapper enforce policy while keeping mechanics private?
- Is
pub(crate),pub(super), or narrower access sufficient? - Would public exposure create a compatibility or security obligation?
- Are tests trying to inspect internals instead of public behaviour?
- Does the public method document effects, errors, and idempotency?
The core principle is that visibility grants capability. E0624 marks a caller asking for a capability the module has not promised. I expose it only after defining the stable behaviour and keep internal steps private when they preserve correctness or room to evolve.