What
Reorganising a module today invalidates every consumer of the declarations that moved, even when the public API is byte-for-byte unchanged and every moved item is re-exported under its original path.
Splitting foo.incn into foo/mod.incn plus foo/detail.incn, with foo/mod.incn re-exporting everything, changes nothing a source-level consumer can see. It still renames every symbol that moved, still changes the unit's semantic digest, and still forces every dependent to recompile.
Why it happens
Two independent encodings of the declaring module path reach the final symbol, and both are ours:
- RFC 120's
incan-v1 projection encodes the complete CanonicalSymbolId, and encode_origin writes SymbolOrigin::Module(path) segments into the payload. Ordinary source functions emit as fn __incan_v1_<payload>; the codegen snapshot suite asserts this.
- Emitted Rust mirrors Incan modules one-to-one (
use crate::functions::col;, with the __INCAN_INSERT_MODS__ insertion point), so rustc mangles a def path we chose. There is no #[no_mangle] or #[export_name] in the emitter.
CanonicalSymbolId::origin also reaches the semantic digest through Body::canonical, so the unit's own identity moves too — not only its symbol.
Worth being precise about whose constraint this is: rustc imposes neither encoding. The genuinely external part is narrower — rustc has no "the dependency changed but my output need not", so any change to an upstream rlib recompiles downstream. That means holding symbol names stable while still producing a different rlib buys nothing. The property only pays if the split produces an identical unit, which means the Incan module structure must not reach the artifact at all.
What would fix it
Key identity on the exported path rather than the declaring path, at both layers:
- emit flat, with item names derived from the exported path;
- key
SymbolOrigin on the exported path so the semantic digest does not move either.
A split preserving the public API then becomes invisible end to end: the upstream unit does not rebake, so no dependent ever sees a changed rlib.
Why this is not a small change
- Two declarations named
foo in different submodules collide at a flat root and need a disambiguator that is not the declaring path.
- Backtraces, debug info, and
incan inspect rust output all get less legible when the symbol stops naming where the code lives.
- A declaration exported under two paths, or none, needs a defined answer.
SymbolOrigin is specified by RFC 120, which is closed. This is a superseding RFC, not a patch.
Evidence
Pinned by moving_a_declaration_into_a_submodule_changes_its_emitted_symbol in crates/incan_semantics_core/src/emitted_symbol.rs, and stated on encode_incan_symbol_identity so the property is met where it is created.
Recommendation
Not now. The cost is concentrated in naming and debuggability, and the benefit only materialises for projects that reorganise modules often. Nothing measured so far says that is common enough to pay for. Filed so the option is on record with its real price rather than rediscovered as a defect — a module split rebaking dependents is correct behaviour under the current model, not a bug.
What
Reorganising a module today invalidates every consumer of the declarations that moved, even when the public API is byte-for-byte unchanged and every moved item is re-exported under its original path.
Splitting
foo.incnintofoo/mod.incnplusfoo/detail.incn, withfoo/mod.incnre-exporting everything, changes nothing a source-level consumer can see. It still renames every symbol that moved, still changes the unit's semantic digest, and still forces every dependent to recompile.Why it happens
Two independent encodings of the declaring module path reach the final symbol, and both are ours:
incan-v1projection encodes the completeCanonicalSymbolId, andencode_originwritesSymbolOrigin::Module(path)segments into the payload. Ordinary source functions emit asfn __incan_v1_<payload>; the codegen snapshot suite asserts this.use crate::functions::col;, with the__INCAN_INSERT_MODS__insertion point), so rustc mangles a def path we chose. There is no#[no_mangle]or#[export_name]in the emitter.CanonicalSymbolId::originalso reaches the semantic digest throughBody::canonical, so the unit's own identity moves too — not only its symbol.Worth being precise about whose constraint this is: rustc imposes neither encoding. The genuinely external part is narrower — rustc has no "the dependency changed but my output need not", so any change to an upstream rlib recompiles downstream. That means holding symbol names stable while still producing a different rlib buys nothing. The property only pays if the split produces an identical unit, which means the Incan module structure must not reach the artifact at all.
What would fix it
Key identity on the exported path rather than the declaring path, at both layers:
SymbolOriginon the exported path so the semantic digest does not move either.A split preserving the public API then becomes invisible end to end: the upstream unit does not rebake, so no dependent ever sees a changed rlib.
Why this is not a small change
fooin different submodules collide at a flat root and need a disambiguator that is not the declaring path.incan inspect rustoutput all get less legible when the symbol stops naming where the code lives.SymbolOriginis specified by RFC 120, which is closed. This is a superseding RFC, not a patch.Evidence
Pinned by
moving_a_declaration_into_a_submodule_changes_its_emitted_symbolincrates/incan_semantics_core/src/emitted_symbol.rs, and stated onencode_incan_symbol_identityso the property is met where it is created.Recommendation
Not now. The cost is concentrated in naming and debuggability, and the benefit only materialises for projects that reorganise modules often. Nothing measured so far says that is common enough to pay for. Filed so the option is on record with its real price rather than rediscovered as a defect — a module split rebaking dependents is correct behaviour under the current model, not a bug.