cargo shear finds the dependencies your Rust crate declares but never uses
Remove unused dependencies in a Rust project
At a glance
- What is it?
- cargo-shear is a Cargo subcommand that reads a project's manifest and source to report dependencies that are declared but never imported, dependencies sitting in the wrong section, and Rust files no module tree reaches, and it can rewrite the manifest to fix what it finds. It suits Rust projects that want a thin manifest and a CI check, and its blind spot is macro expansion, which it does not perform by default.
- Who is it for?
- Rust projects whose manifests have grown by copy-paste and whose CI already runs cargo check are the people cargo shear is for, and the fact that it works by static analysis without compiling means one run on Linux covers every target you build for. It is the wrong tool for a codebase that hides its dependencies behind macro expansion, since the default parser does not expand macros and the --expand flag that does requires nightly and is much slower.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 5 days ago.
- What is it written in?
- Mainly Rust, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Three problems in one manifest check
The tool covers three distinct failure modes that all live in Cargo.toml. Unused dependencies are entries the manifest declares that no source file imports, and they are the headline case. Misplaced dependencies are dev or build dependencies sitting in the wrong section, which is a different bug with a different fix, since the section controls when a crate is compiled. Unlinked source files are Rust files that no module tree reaches, which is a different problem again, since a file can be dead code that no manifest entry explains. The README opens with the description of the tool as detecting and fixing issues in Rust projects, and it names the alternative tools in the package keywords, udeps and machete, which tells you the category it is competing in. The distinction that matters in practice is between the first problem, which is a manifest problem, and the third, which is a source tree problem, and a tool that only looked at the manifest would miss it.
The analysis is static, so one run covers every target platform
The CI section states the property that makes this practical in a multi-platform project. The tool uses static analysis and operates on source code without compiling, and it means it only needs to run once on a single platform such as Linux to detect issues across all target platforms, including those with platform-specific dependencies and conditional compilation. That is a real advantage over a check that compiles the project, since a compile-based approach gives you an answer for the host you ran it on and nothing about the others. The one exception is named precisely, and it is the --expand flag, which invokes cargo build and may therefore produce platform-specific results. So the static property is a property of the default mode, not a property of the tool, and if you enable macro expansion you give up the cross-platform guarantee. The technique is described in the same section, starting with using the cargo_metadata crate to list all dependencies specified in workspace and package dependency sections, and then iterating through all package targets, which are lib, bin, example, test and bench.
Installing and running it takes one command
The installation is three alternatives and the usage is a subcommand, so there is no configuration to write before you can see results. The pre-built binary path uses cargo binstall, and the source path uses cargo install, and there is a Homebrew formula as well.
# Install from pre-built binaries.
cargo binstall cargo-shear
# Build from source.
cargo install cargo-shear
# Install from brew.
brew install cargo-shearRunning it with no arguments reports issues and changes nothing, and adding --fix rewrites the manifest to remove what it found.
cargo shearcargo shear --fixThe exit code table makes this usable as a gate. Without --fix, exit code 0 means no issues found and 1 means issues found, with 2 reserved for an error during processing, and with --fix the code 1 case means issues were found and fixed. So a CI job that runs cargo shear and fails on a non-zero exit is a lint for your manifest, and the same run on a developer machine with --fix is a repair. That split between detect and fix is the design choice that makes the tool safe to run on a machine you care about.
Macro expansion is the blind spot, and the escape hatch is slow
The Limitations section names the problem honestly. The tool cannot detect hidden imports from macro expansions without the --expand flag, which is nightly only, and the reason given is that cargo shear uses rust-analyzer's parser to parse files and does not expand macros by default. The flag exists and works.
cargo shear --expand --fixBut the --expand flag uses cargo expand, which requires nightly and is significantly slower, so the cost of correctness is a nightly toolchain and a much longer run in the exact project that most needs the check. The practical consequence is that a codebase with a macro that generates use statements will report those dependencies as unused, and the --fix mode would then remove them and break the build. The second limitation is narrower. Misplaced dependency detection only works for integration tests, benchmarks and examples, and unit test dependencies within a cfg(test) block cannot be detected as misplaced. So the three checks do not have equal coverage, and the one that people most often want, is a dev-dependency in the wrong place, is the one that will not fire for in-crate unit tests.
Ignoring false positives needs the right scope
The configuration section is the part that turns a linter into something a team can live with, and it has a scope rule that is easy to get wrong. A package ignore under package metadata applies only to that package, while a workspace ignore under workspace metadata applies to every member and to the workspace dependency itself, and the ignored-paths globs are matched relative to the workspace root. Both forms take the same two keys.
[package.metadata.cargo-shear]
ignored = ["crate-name"][package.metadata.cargo-shear]
ignored-paths = ["src/proto/*.rs", "examples/old/*"]A useful detail is that an ignore which suppresses nothing is reported as redundant so it can be removed, and a workspace ignore counts as redundant only when no member needs it, which means a dependency used in one crate but unused in another stays covered by the workspace ignore. Without that rule, the natural cleanup of an over-broad ignore would break the crate that still needs it. There is a third switch for a specific workflow, keep-path-dependencies, which treats a workspace dependency entry with a path as used as long as a crate exists at the declared path, for teams that declare a crate in the workspace section before any member depends on it, while a stale entry whose crate has been deleted or moved is still reported.
cargo-hakari workspace-hack crates are detected without configuration
One special case is handled for you. cargo-hakari generates a workspace-hack crate that declares many dependencies it never imports, in order to unify Cargo features, and that crate is depended on by every workspace member without being imported. A naive unused-dependency checker would flag both the crate and the dependency edges pointing at it. The tool detects such a crate automatically, via the BEGIN HAKARI SECTION marker in its Cargo.toml, and skips both the crate itself and the edges pointing at it, so no ignored configuration is needed. The mechanism is a marker rather than a heuristic, which is the right way to do it, because a heuristic would eventually misfire on a crate that happens to declare a lot of dependencies. The other end of that design is stated in the same breath: anything else that looks like a workspace-hack crate should be reported as a bug.
JSON output and lib target flags are the CI-facing parts
Two features exist specifically for machine consumption. The --format=json flag produces machine-readable output, described as particularly useful for CI pipelines and custom tooling that need to process the results programmatically, and the output has a defined shape with a summary carrying counts of errors, warnings and fixes, and a findings array where each issue carries a code such as shear/unused_dependency, a severity, a message, a file path, a location with byte offset and length, a help field with a suggested fix, and a fixable boolean telling you whether --fix can handle it. The second is --check-test-targets, which detects mismatches between the lib target settings and the source content, warning when test = false is paired with source that contains tests, or doctest = false with source containing doc tests, and within a workspace warning when test and doctest are left at their default of true for lib targets that contain none. It is disabled by default and pairs with --fix to reconcile the flags. Both features are about a manifest that has drifted away from the code, which is the same category of problem the unused dependency check addresses.
Where cargo shear is the wrong choice
The first wrong case is a project whose dependencies are only used through macros, since the default parser will call them unused and the --fix mode will remove them. The second is a repository where the manifest is intentionally broad, a workspace that declares a set of shared dependencies for future use across members, where the keep-path-dependencies switch helps only for path entries and not for version-only declarations. The third is a codebase that needs its dependencies checked by compiling, because static analysis cannot tell you whether a crate actually links on a given target, and a project with conditional compilation and target-specific dependencies may want a real build. The fourth is a team that already has a working answer, since a project that runs cargo machete or cargo udeps in CI gets the same class of check, and the reason to switch is the extra coverage of unlinked files and misplaced dependencies rather than a better unused check. The fifth is anything outside Rust, since the whole thing is a Cargo subcommand and the manifest model it reasons about is Cargo's.
Editorial conclusion
Rust projects whose manifests have grown by copy-paste and whose CI already runs cargo check are the people cargo shear is for, and the fact that it works by static analysis without compiling means one run on Linux covers every target you build for. It is the wrong tool for a codebase that hides its dependencies behind macro expansion, since the default parser does not expand macros and the --expand flag that does requires nightly and is much slower. Check three things before wiring it into a build: that your project has no macro-generated imports, because those will look unused, that your workspace metadata section is where you expect it, since the package and workspace ignore scopes differ, and that the diagnostic code and severity fields in the JSON output match what your CI parses. The licence is MIT, the newest release is v1.14.0 from 2026-09-22, the project states it is feature-complete, and the last push was on 2026-10-18.
Frequently asked questions
What does cargo shear check in a Rust project?
Three things: unused dependencies in Cargo.toml, misplaced dependencies such as dev or build dependencies in the wrong section, and unlinked source files that no module tree reaches. It can fix what it finds with the --fix flag.
How do I install and run cargo shear?
Install it with cargo binstall cargo-shear, cargo install cargo-shear, or brew install cargo-shear. Running cargo shear reports issues without changing anything, and cargo shear --fix rewrites the manifest.
Why does cargo shear report a dependency as unused when a macro uses it?
The tool uses rust-analyzer's parser and does not expand macros by default, so it cannot detect hidden imports from macro expansions. The --expand flag uses cargo expand to work around this, but it requires nightly and is significantly slower.
How do I ignore a false positive in cargo shear?
Add the crate name to the ignored list in package metadata for a single package, or in workspace metadata to cover every member and the workspace dependency itself. Glob patterns for unlinked files go in ignored-paths, and an ignore that suppresses nothing is reported as redundant.
What does cargo shear --check-test-targets do?
It warns about mismatches between lib target settings and source content, such as test = false paired with source containing tests or doctest = false paired with doc tests, and within a workspace when test and doctest are left at their default of true for lib targets with none. It is disabled by default and works with --fix.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/boshen-cargo-shear)Was this useful?