modum
It is a lint tool. It reports diagnostics. It does not rewrite code.
Why It Exists
These API shapes are legal Rust, but they add noise:
use Client;
use UserRepository;
pub use Error;
These read more clearly:
use http;
use user;
pub use Error;
And these public paths usually read better:
user::Repository
user::error::InvalidEmail
partials::Error
instead of:
user::UserRepository
user::error::InvalidEmailError
partials::error::Error
modum enforces that style across an entire workspace.
Mental Model
modum follows three rules:
- Keep namespace context visible at call sites.
- Keep public paths meaningful without redundancy.
- Use modules for domain boundaries, not file organization.
Quick Usage
cargo install modum installs both modum and the Cargo subcommand cargo-modum, so either of these is valid:
If you are developing modum itself:
Environment:
MODUM=off||
Default mode is deny.
CI Usage
Use modum the same way you would use clippy or cargo-deny: run it as a normal command in CI, not from build.rs.
- run: cargo install modum
- run: cargo modum check --root .
Exit Behavior
0: clean, or warnings allowed via--mode warn2: warning-level policy violations found indenymode1: hard errors, including parse/configuration failures and error-level policy violations such asapi_organizational_submodule_flatten
Configuration
Configure the lints in any workspace with Cargo metadata:
[]
= ["Id", "Repository", "Service", "Error", "Command", "Request", "Response"]
= ["storage", "transport", "infra", "common", "misc", "helpers", "helper", "types", "util", "utils"]
= ["common", "misc", "helpers", "helper", "types", "util", "utils"]
= ["error", "errors"]
= ["auth", "command", "components", "email", "error", "http", "page", "partials", "policy", "query", "repo", "store", "storage", "transport", "infra"]
Use [package.metadata.modum] inside a member crate to override workspace defaults for that package.
Tuning guide:
generic_nouns: generic leaves likeRepository,Error, orRequestnamespace_preserving_modules: modules that should stay visible at call sites, such ashttp,email,partials, orcomponentsorganizational_modules: modules that should not leak into the public API surface, such aserror
Lint Categories
Import Style
These warn when imports or re-exports flatten a namespace that should stay visible.
namespace_flat_usenamespace_flat_use_preserve_modulenamespace_flat_use_redundant_leaf_contextWarning for flattened imports or rename-heavy aliases whose leaf repeats parent context. For plain imports, this only fires when the shorter leaf would be an actionable generic noun such asRepository,Error, orId.namespace_redundant_qualified_genericWarning for qualified call-site paths whose module only repeats a generic category already named by the leaf, such asresponse::Responseorerror::Error.namespace_parent_surfacenamespace_flat_pub_usenamespace_flat_pub_use_preserve_modulenamespace_flat_pub_use_redundant_leaf_context
Examples:
use storage::Repository;use http::Client;use user::UserRepository;response::Responseuse crate::error::Error;inside a crate whose root surface already exposesErrorpub use auth::{login, logout};
Canonical parent-surface re-exports are allowed. pub use error::{Error, Result}; is valid when that is how a module intentionally exposes module::Error and module::Result. The same applies to broader UI surfaces such as exposing both components::Button and partials::Button.
Public API Paths
These warn when public leaves are too generic for a weak parent, or when the path repeats context it already has.
api_missing_parent_surface_exportapi_weak_module_generic_leafapi_redundant_leaf_contextapi_redundant_category_suffix
Examples:
partials::button::Buttonwhen the intended surface should also exposepartials::Buttonstorage::Repositoryuser::UserRepositoryuser::error::InvalidEmailError
Private organizational child modules are allowed to flatten their family items back to the parent surface. For example, mod auth_shell; pub use auth_shell::{AuthShell, AuthShellVariant}; is treated as a valid parent-surface export shape.
Module Boundaries
These catch weak or redundant public module structure.
api_catch_all_moduleapi_repeated_module_segment
Examples:
helperserror::error
Structural Errors
This rule is an error, not a warning.
api_organizational_submodule_flatten
Example:
partials::error::Errorshould usually bepartials::Error
What It Does Not Check
Some naming-guide rules stay advisory because they are too semantic to lint reliably without compiler-grade context.
Examples:
- choosing the best public path among several plausible domain decompositions
- deciding when an internal long name plus
pub use ... as ...is the right tradeoff - deciding whether a new module level adds real meaning or only mirrors the file tree in edge cases
Scope
Default discovery:
- package root: scans
<root>/src - workspace root: scans each member crate's
src
Override discovery with --include:
False Positives And False Negatives
The broader import-style lints only inspect module-scope use items. They do not scan local block imports inside functions or tight test scopes, because those scopes often benefit from flatter imports.
To reduce false negatives:
- extend
namespace_preserving_modulesfor domain modules likeuser,billing, ortenant - keep
generic_nounsaligned with the generic leaves your API actually uses - keep
organizational_modulesconfigured sopartials::error::Error-style paths stay blocked