Code Story

Chapter #9 – Modules & Namespaces

module declaration, visibility, imports, packages and crates

9.0  Prologue

Modules and namespaces partition a program’s names into groups, preventing collisions between libraries and providing a logical structure that mirrors the domain. The module system also controls visibility: which items are accessible outside their defining unit and which are implementation details.

9.1  Module Declaration

Each language provides a way to declare a named scope that groups related items. // Rust: mod keyword; module tree mirrors file system mod geometry { pub struct Point { pub x: f64, pub y: f64 } pub fn distance(a: &Point, b: &Point) -> f64 { /* ... */ } } // In separate file: src/geometry.rs (or src/geometry/mod.rs) mod geometry; // loads src/geometry.rs // C++: namespace (no file-system correspondence) namespace geometry { struct Point { double x, y; }; double distance(const Point& a, const Point& b); } // C#: namespace (typically matches folder structure by convention) namespace Geometry { public class Point { public double X, Y; } } // Python: module = file; package = directory with __init__.py # geometry.py -> import geometry; geometry.distance(...) Rust’s module system is unique in that the module hierarchy must be explicitly declared; the compiler does not automatically discover files. This prevents accidental inclusion of test utilities or platform-specific code.

9.2  Visibility and Access Modifiers

Visibility controls which modules can name an item. Default visibility differs per language:
Language Default Public Restricted
Rust private to module pub pub(crate), pub(super), pub(in path)
C++ struct: public; class: private public: protected:, private:
C# private (members); internal (types) public internal, protected, private protected
Python public (convention only) - _name (private by convention), __name (name-mangled)
Rust’s pub(crate) restricts visibility to the current crate, enabling a public API surface for downstream consumers while keeping implementation details internal.

9.3  Imports and Use Declarations

Importing a name brings it into the current scope, avoiding fully-qualified paths. // Rust: use paths; glob import available but discouraged use std::collections::HashMap; use std::io::{self, Read, Write}; // C++: using declarations and directives using std::vector; // single name using namespace std; // entire namespace (discouraged in headers) #include <vector> // textual inclusion; not a module import // C++20: named modules (replaces #include for new code) import std; // C# using System.Collections.Generic; using Dict = System.Collections.Generic.Dictionary<string,int>; // Python import os from pathlib import Path import numpy as np # alias for long names C++ header inclusion via #include is textual substitution, not a true module system. C++20 modules provide proper isolation but adoption is still gradual.

9.4  Packages and Crates

Beyond files and modules, languages provide a unit of distribution and versioning:
Language Unit Registry Manifest
Rust crate (library or binary) crates.io Cargo.toml
C++ library (no standard) vcpkg, Conan CMakeLists.txt / conanfile
C# assembly / NuGet package nuget.org .csproj
Python package (directory or wheel) PyPI pyproject.toml
Rust’s Cargo integrates dependency resolution, building, testing, and publishing in a single tool, making crate management straightforward. The lock file (Cargo.lock) pins exact dependency versions for reproducible builds.

9.5  Epilogue

A well-designed module system keeps large codebases navigable by enforcing clear boundaries between components. Visibility rules make the public API explicit, protecting internal invariants. The next chapter covers concurrency - coordinating multiple threads of execution within one program.

9.6  References

Rust Module System - The Book
C++ Namespaces - cppreference
C# Namespaces - Microsoft
Python Import System