9. Modules and organization (pocket version)

TL;DR (5 lines)

  • Modules are ownership boundaries.
  • Imports should reveal architecture, not hide it.
  • Public surface and internal detail should be easy to distinguish.
  • Import misuse is a structural error, not style noise.
  • Good module docs connect layout and responsibility.

Concrete Problem

Module chapters often stop at import syntax and never explain ownership boundaries across files and packages.

Coherent example

space demo/modules

use demo/report.{build_summary} as report

proc run() -> int {
  give report.build_summary()
}

proc main(args: list[string]) -> int {
  give run()
}

export *

Complete examples

Each block below is a complete reading unit with its own boundary, data shape, and observable result.

Primary coherent example

This is the compact chapter anchor used by the surrounding explanation.

space demo/modules

use demo/report.{build_summary} as report

proc run() -> int {
  give report.build_summary()
}

proc main(args: list[string]) -> int {
  give run()
}

export *

Facade module boundary

This complete block models a small facade that keeps the public surface narrow.

space examples/modules/facade

use examples/modules/report.{build_summary} as report

proc run_report() -> int {
  let status: int = report.build_summary()
  if status != 0 { give status }
  give 0
}

proc main(args: list[string]) -> int {
  give run_report()
}

export *

Local module responsibility

This module keeps validation local and exposes one stable procedure.

space examples/modules/report

form ReportConfig {
  name: string
  rows: int
}

proc build_summary() -> int {
  let cfg: ReportConfig = ReportConfig { name: "daily", rows: 4 }
  if cfg.name == "" { give 11 }
  if cfg.rows <= 0 { give 12 }
  give 0
}

export *

Immediate work for this chapter

  • Show one facade module and one local responsibility module.
  • Keep imports at the module surface, not inside local procedure bodies.
  • Make public surface smaller than implementation detail.
  • Keep the first code block complete and aligned with current Vitte docs syntax.
  • Keep the invalid block focused on one broken contract only.
  • Replace generic prose with one concrete rule visible in the code.
  • Keep every example small enough to review from top to bottom.

Chapter override: 09-modules.html

Dedicated problem

This chapter must teach modules as ownership boundaries. The reader should see facade modules, local responsibility modules, imports, and exported surfaces as architecture rather than file decoration.

Specific complete examples

Facade entry module

The entry-facing module imports one public service and keeps orchestration small.

space examples/modules/facade_entry

use examples/modules/service.{run_service} as service

proc run() -> int {
  give service.run_service()
}

proc main(args: list[string]) -> int {
  give run()
}

export *

Service responsibility module

The service owns validation and exposes one stable public procedure.

space examples/modules/service

form ServiceConfig {
  name: string
  workers: int
}

proc run_service() -> int {
  let cfg: ServiceConfig = ServiceConfig { name: "api", workers: 2 }
  if cfg.name == "" { give 11 }
  if cfg.workers <= 0 { give 12 }
  give 0
}

export *

Narrow import surface

The consumer imports only the symbol it needs.

space examples/modules/consumer

use examples/modules/service.{run_service} as service

proc check() -> int {
  let status: int = service.run_service()
  if status != 0 { give status }
  give 0
}

export *

Risks and diagnostics

RiskDiagnostic signalAction
Oversized public surfaceA module exports internals and makes coupling permanent.Expose one facade procedure and keep helpers local.
Hidden dependencyAn import appears inside local logic and hides architecture.Keep imports at the module surface.
Cycle pressureTwo modules own each other's responsibilities.Move shared contracts to a third boundary or reduce the split.

Review checklist

  • Imports appear before behavior.
  • Public surface is smaller than implementation detail.
  • The example shows at least two module roles.
  • The invalid case shows a structural import or ownership failure.

Production use

  • Use facades for stable project-level boundaries.
  • Keep domain validation in the module that owns the data.
  • Review exports as API commitments, not convenience shortcuts.

What to avoid

  • Do not import entire module trees when one symbol is enough.
  • Do not use module splits to hide unclear ownership.
  • Do not move imports into procedures to make examples look shorter.

Global explanation

The point of a modules chapter is not the import token itself. The point is ownership: what belongs together, what should stay private, and what the public surface of a module should look like.

Invalid case

proc bad_module() -> int {
  use demo/report
  give 0
}

This invalid case stays small so the broken contract remains visible.

Common pitfalls

  • Using module pages as import syntax cheat sheets only.
  • Importing too much because boundaries were never designed.
  • Putting module-surface declarations inside local blocks.

Short exercise

Split one procedure from the example into another logical module and keep the public boundary explicit.

Summary in 5 points

  1. Modules are ownership boundaries.
  2. Imports should reveal architecture, not hide it.
  3. Public surface and internal detail should be easy to distinguish.
  4. Import misuse is a structural error, not style noise.
  5. Good module docs connect layout and responsibility.

Next best action

Keep the example small, reproduce it locally, then continue to the full chapter if you need the broader context.

Chapter deep dive

9. Modules and organization turns file organization into ownership. The chapter is written for a reader deciding what belongs together.

The practical boundary is: private helper, public surface, and imported dependency. Keep that boundary in view while reading the example, the invalid case, and the exercise.

Role in the learning path

Module chapters often stop at import syntax and never explain ownership boundaries across files and packages.

A tiny application is split into a domain module, a service module, and an entry module.

This chapter helps the reader structure code so that imports reflect ownership instead of accident.

Profile-specific deep dive

Public surface

  • Imports belong at the module surface so ownership is visible before behavior.
  • The facade should expose one stable procedure rather than every helper.
  • Public names should describe responsibility, not file layout.

Private implementation

  • Validation and construction can remain local to the module that owns the data.
  • Implementation helpers should stay behind the facade until another module needs them.
  • A module split is justified only when it reduces coupling or clarifies ownership.

Layout discipline

  • Use one module for entry orchestration and another for domain responsibility.
  • Avoid imports inside procedure bodies unless the docs intentionally teach that exception.
  • Keep circular dependency risks visible in the invalid case or diagnostics.

Reading the valid example

  1. space demo/modules: names the ownership boundary before any behavior appears.
  2. use demo/report.{build_summary} as report: declares an external dependency instead of hiding it inside the flow.
  3. proc run() -> int {: states the callable contract: inputs first, result shape last.
  4. give report.build_summary(): ends the local path with an explicit result.
  5. }: supports the chapter contract without adding hidden behavior.
  6. proc main(args: list[string]) -> int {: states the callable contract: inputs first, result shape last.
  7. give run(): ends the local path with an explicit result.
  8. }: supports the chapter contract without adding hidden behavior.
  9. export *: supports the chapter contract without adding hidden behavior.

Lesson from the invalid example

  1. proc bad_module() -> int {: this line helps isolate the failure because it states the callable contract: inputs first, result shape last.
  2. use demo/report: this line helps isolate the failure because it declares an external dependency instead of hiding it inside the flow.
  3. give 0: this line helps isolate the failure because it ends the local path with an explicit result.
  4. }: this line helps isolate the failure because it supports the chapter contract without adding hidden behavior.

Engineering decisions to preserve

  • Name the boundary before changing code: Modules are ownership boundaries.
  • Keep the smallest example executable: Imports should reveal architecture, not hide it.
  • Make the invalid path explain one failure only: Public surface and internal detail should be easy to distinguish.
  • Prefer a visible contract over an implied convention: Import misuse is a structural error, not style noise.
  • Leave a review anchor that another maintainer can verify: Good module docs connect layout and responsibility.

Context-specific review criteria

  • The page makes the private helper, public surface, and imported dependency boundary visible before the first code block.
  • The intended reader, a reader deciding what belongs together, can follow the valid example through named contracts instead of memorized tokens.
  • The invalid example fails for the same reason the prose discusses.
  • The exercise extends the same contract instead of introducing an unrelated concept.
  • The next chapter can reuse the vocabulary introduced here without redefining it.
  • The chapter stays specific enough that its title materially changes the meaning of the page.
  • Every warning connects to a concrete code shape.
  • The summary leaves one durable engineering rule behind.

Contract matrix

ConcernChapter ruleEvidence to keep
OwnershipCode belongs behind the boundary named by the chapter.The chapter keeps ownership visible through private helper, public surface, and imported dependency.
Input contractThe procedure receives a shape that is named before branching.The valid example names the accepted shape before branching.
Nominal pathThe clean path remains readable without hidden state.The successful result can be found without reading hidden state.
Failure pathThe invalid case isolates one failure reason.The broken example has one main reason to fail.
NamingNames explain the domain rather than only the mechanism.Names remain tied to the chapter goal.
TypesTypes remove ambiguity from values and results.Fields and return values carry domain meaning.
Control flowBranches stay traceable from guard to result.Guards appear before the result they protect.
Module boundaryThe public surface stays smaller than implementation detail.The public surface remains smaller than the implementation detail.
Diagnostic valueThe failure path points back to the exact contract.The invalid example points back to the exact contract.
Test valueRegression evidence covers one passing path and one failing path.One passing case and one failing case cover the lesson.
Refactor valueImplementation cleanup preserves the result shape.The result shape stays stable during local cleanup.
Publication valueThe chapter leaves one concrete engineering rule.The chapter leaves one concrete engineering rule.

Rewrite path for this chapter

  1. Rewrite the opening paragraph so it names private helper, public surface, and imported dependency before naming syntax.
  2. Keep the valid example small enough that the full contract fits on screen.
  3. Move any broad claim back to a specific line in the example.
  4. Preserve one invalid case that fails for the chapter's main reason.
  5. Add one sentence explaining why the invalid case is not a random error.
  6. Make every pitfall actionable by naming the code shape it damages.
  7. Keep the exercise inside the same domain as the example.
  8. Avoid introducing a second unrelated project just to show variety.
  9. Use the summary to restate the chapter rule, not the table of contents.
  10. Check that the next chapter can build on this vocabulary.
  11. Remove any sentence that would still be true in every other chapter.
  12. Keep the last action small, local, and testable.

Diagnostic anchors

  • The first inspected line is the one that declares the chapter's main contract.
  • The central type, field, procedure, or branch carries the chapter's main idea.
  • The invalid example includes a sentence-level explanation of its failure.
  • Refactors preserve the detail that would otherwise mislead a future reader.
  • The behavior that must stay stable is named before implementation changes begin.
  • Vague names are replaced before they become review friction.
  • Regression coverage protects the chapter's main contract.
  • Implementation details stay out of public API unless the chapter explicitly teaches that surface.
  • Beginner-facing diagnostics point to the contract, not to a random syntax detail.
  • The next chapter can assume one clearly named concept from this page.

When extending this chapter

  • Extend toward a reader deciding what belongs together, not toward a broader catalog of features.
  • Add a second example only if it sharpens the same contract.
  • Prefer a small variant over a new subsystem.
  • Keep prose close to code; every abstract claim should point to a visible shape.
  • Do not hide a new concept in the exercise.
  • If a paragraph explains policy, add the concrete code boundary it protects.
  • If a paragraph explains syntax, add the semantic reason the syntax matters.
  • If a paragraph explains architecture, identify the owner of each boundary.
  • If a paragraph explains failure, keep the failing line close to the explanation.
  • Stop expanding when the chapter has one complete, testable lesson.

Failure modes to avoid

  • Using module pages as import syntax cheat sheets only.
  • Importing too much because boundaries were never designed.
  • Putting module-surface declarations inside local blocks.

Practice scenario

Start from the coherent example in 9. Modules and organization. Change one identifier, one guard, and one returned value. After each change, write down whether the public contract is still the same contract or a new one.

If the contract changed, update the type or result shape first. If only the implementation changed, keep the external name stable and add one regression note explaining what should not change again.

Before moving on

  • You can state the chapter role: turns file organization into ownership.
  • You can point to the main boundary: private helper, public surface, and imported dependency.
  • You can connect the invalid case to the problem statement: Module chapters often stop at import syntax and never explain ownership boundaries across files and packages.
  • You can perform the exercise: Split one procedure from the example into another logical module and keep the public boundary explicit.