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:
Repository
InvalidEmail
Error
instead of:
UserRepository
InvalidEmailError
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:
Neovim
modum works well with nvim-lint. Use --mode warn so diagnostics do not fail the editor job, and use --format json for stable parsing.
local lint = require
lint.. =
lint.. =
If you edit multiple crates from one Neovim session, replace vim.fn.getcwd() with your workspace root resolver. modum is workspace-oriented, so it is usually better to run it on save than on every InsertLeave.
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_useWarning for flattened imports of generic nouns when there is an actionable namespace-visible call-site form that adds net context, such asstorage::Repositoryorhttp::StatusCode. It skips cases where the only preserved form would still be redundant, such aserror::Errororresponse::Response.namespace_flat_use_preserve_moduleWarning for flattened imports from configured namespace-preserving modules when the preserved call-site form still adds net context.namespace_flat_use_redundant_leaf_contextWarning for flattened imports or actionable 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. For rename aliases, this only fires when the qualified form would still preserve real context, such ashttp::StatusCodeorpage::Event.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