Project structure: crates, workspaces and features
Modules showed how code is split inside one crate. This page covers the next sizes up, controlled from Cargo.toml: a package with a library and programs, a workspace of several packages, and features that switch parts of a package on and off. The project is a small word counter, wordstat.
RememberA package has a Cargo.toml. A workspace shares one Cargo.lock and one target/ between packages. A feature is a name in [features] that code can test with cfg(feature = "...").
wordstat/ ├── crates/ │ ├── wordstat/ │ │ ├── src/ │ │ │ ├── bin/ │ │ │ │ └── dump.rs a second program │ │ │ ├── count.rs │ │ │ ├── error.rs │ │ │ ├── json.rs │ │ │ ├── lib.rs the library crate │ │ │ └── main.rs a program that uses the library │ │ ├── tests/ │ │ │ └── cli.rs an integration test │ │ └── Cargo.toml │ └── wordstat-report/ │ ├── src/ │ │ └── main.rs another package, a user of the library │ └── Cargo.toml ├── Cargo.lock one for the whole workspace ├── Cargo.toml the workspace: members, shared settings ├── clippy.toml linter settings └── rustfmt.toml formatter settings
[workspace]members = ["crates/*"]resolver = "3"
[workspace.package]version = "0.1.0"edition = "2024"rust-version = "1.85"license = "MIT OR Apache-2.0"
[workspace.dependencies]serde_json = "1.0"wordstat = { path = "crates/wordstat", version = "0.1.0" }
[workspace.lints.rust]unsafe_code = "forbid"missing_docs = "warn"
[workspace.lints.clippy]dbg_macro = "warn"[package]name = "wordstat"description = "Count the words in a piece of text"default-run = "wordstat"version.workspace = trueedition.workspace = truerust-version.workspace = truelicense.workspace = true
[features]# Off by default: `Stats::to_json` and the `dump` program need serde_json.json = ["dep:serde_json"]
[dependencies]serde_json = { workspace = true, optional = true }
[[bin]]name = "dump"required-features = ["json"]
[lints]workspace = true[package]name = "wordstat-report"version.workspace = trueedition.workspace = truerust-version.workspace = truelicense.workspace = truepublish = false
[dependencies]wordstat = { workspace = true, features = ["json"] }
[lints]workspace = trueA library and programs in one package
Section titled “A library and programs in one package”src/lib.rs is the library crate, named after the package. src/main.rs and the files in src/bin/ are programs, and each file in tests/ is an integration test, compiled as a separate crate (Cargo targets; Testing shows tests running). The programs and tests call the library through its public items, as any other crate would. This is how Cargo sees the workspace, printed by cargo metadata (docs):
$ cargo metadata --format-version 1 --no-deps # then print each package and its targets
wordstat ['lib wordstat', 'bin dump', 'bin wordstat', 'test cli']
wordstat-report ['bin wordstat-report']
With two programs, default-run = "wordstat" names the one a plain cargo run starts (manifest). The library:
//! Count the words in a piece of text.//!//! ```//! let stats = wordstat::count_words("the cat and the dog")?;//! assert_eq!(stats.total(), 5);//! assert_eq!(stats.get("the"), 2);//! # Ok::<(), wordstat::Error>(())//! ```
mod count;mod error;#[cfg(feature = "json")]mod json;
pub use count::{Stats, count_words};pub use error::Error;use std::collections::BTreeMap;
use crate::Error;
/// How often each word appeared, with the words in alphabetical order.#[derive(Debug, Default)]pub struct Stats { counts: BTreeMap<String, usize>,}
/// Counts the words in `text`, ignoring case and surrounding punctuation.////// # Errors////// Returns [`Error::Empty`] when `text` has no words.pub fn count_words(text: &str) -> Result<Stats, Error> { let mut stats = Stats::default(); for word in text.split_whitespace() { *stats.counts.entry(normalize(word)).or_insert(0) += 1; } if stats.counts.is_empty() { return Err(Error::Empty); } Ok(stats)}
// Only this crate needs to know how words are normalised.pub(crate) fn normalize(word: &str) -> String { word.trim_matches(|c: char| !c.is_alphanumeric()) .to_lowercase()}
impl Stats { /// The number of words counted, repeats included. pub fn total(&self) -> usize { self.counts.values().sum() }
/// How often `word` appeared (0 if it did not). pub fn get(&self, word: &str) -> usize { self.counts.get(word).copied().unwrap_or(0) }
/// The words and their counts, most frequent first; ties in alphabetical /// order. pub fn most_common(&self) -> Vec<(&str, usize)> { let mut all: Vec<_> = self.counts.iter().map(|(w, n)| (w.as_str(), *n)).collect(); all.sort_by(|a, b| b.1.cmp(&a.1).then(a.0.cmp(b.0))); all }
// Visible to the rest of this crate (the `json` module), not to users. #[cfg_attr(not(feature = "json"), allow(dead_code))] pub(crate) fn counts(&self) -> &BTreeMap<String, usize> { &self.counts }}
#[cfg(test)]mod tests { use super::*;
#[test] fn ignores_case_and_punctuation() { let stats = count_words("Go, go! GO?").unwrap(); assert_eq!(stats.get("go"), 3); }
#[test] fn empty_text_is_an_error() { assert_eq!(count_words(" ").unwrap_err(), Error::Empty); }}use std::fmt;
/// What can go wrong when counting words.#[derive(Debug, PartialEq, Eq)]#[non_exhaustive]pub enum Error { /// The text had no words in it. Empty,}
impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Error::Empty => f.write_str("no words to count"), } }}
impl std::error::Error for Error {}//! Prints each word in its arguments with its count, most frequent first.
use std::process::ExitCode;
fn main() -> ExitCode { let text = std::env::args().skip(1).collect::<Vec<_>>().join(" "); match wordstat::count_words(&text) { Ok(stats) => { for (word, n) in stats.most_common() { println!("{n:>3} {word}"); } ExitCode::SUCCESS } Err(e) => { eprintln!("wordstat: {e}"); ExitCode::FAILURE } }}3 the 1 and 1 cat 1 dog$ cargo run -q -p wordstat-report
6 words, as JSON: {"be":2,"not":1,"or":1,"to":2}$ cargo run -p wordstat # no wordswordstat: no words to count
The wordstat-report package depends on wordstat like an outside user (the second step). In the third step there are no words, so the library’s error reaches the program, which exits with status 1.
A workspace shares what should be shared
Section titled “A workspace shares what should be shared”The root Cargo.toml has no [package], only [workspace]. The Cargo Book says “All packages share a common Cargo.lock file which resides in the workspace root” and a common output directory, target (Workspaces). It also recommends one folder of members listed with a glob, members = ["crates/*"], so a new crate needs no edit to the list.
Three tables in the root file are inherited by the members:
[workspace.package]: the version, edition,rust-versionand licence are written once, and each member saysversion.workspace = true.[workspace.dependencies]: a dependency’s version is written once (serde_json = "1.0"), and a member saysserde_json = { workspace = true }. The path dependency onwordstatis declared there too.[workspace.lints]: see Lints, below.
The one lock file has 13 entries, but cargo tree shows 7 packages in this build: the two workspace packages and five crates under serde_json. The other six (serde, serde_derive, syn, quote, proc-macro2, unicode-ident) are there because the lock file also records dependencies a crate can use for optional features; serde_core lists serde_derive. This build does not use them.
$ cargo build --locked $ grep -c "^name = " Cargo.lock # packages in the one lock file 13 $ cargo tree --workspace --prefix none # then without paths, sorted, one line per package itoa v1.0.18 memchr v2.8.3 serde_core v1.0.229 serde_json v1.0.154 wordstat v0.1.0 wordstat-report v0.1.0 zmij v1.0.23 $ sed -n '/^name = "serde_core"/,/^$/p' Cargo.lock # its dependencies, as the lock file lists them dependencies = [ "serde_derive", ]
Packages and crates has the vocabulary.
What the library shows and what it keeps
Section titled “What the library shows and what it keeps”In lib.rs, mod count; is private, and pub use count::{Stats, count_words}; re-exports the two names users need (Re-exports). That way the file layout can change without changing the paths users write. pub(crate) marks things the rest of this crate may use but users may not. Another package can reach neither, and the compiler says so:
error[E0603]: module `count` is private --> crates/wordstat/src/bin/peek.rs:4:26 | 4 | let word = wordstat::count::normalize("Hello!"); | ^^^^^ --------- function `normalize` is not publicly re-exported | | | private module | note: the module `count` is defined here --> crates/wordstat/src/lib.rs:10:1 | 10 | mod count; | ^^^^^^^^^ error[E0624]: method `counts` is private --> crates/wordstat/src/bin/peek.rs:6:35 | 6 | println!("{word} {:?}", stats.counts()); | ^^^^^^ private method | ::: crates/wordstat/src/count.rs:55:5 | 55 | pub(crate) fn counts(&self) -> &BTreeMap<String, usize> { | ------------------------------------------------------- private method defined here Some errors have detailed explanations: E0603, E0624.
The error type is a plain enum Error with Display and std::error::Error written by hand, marked #[non_exhaustive]. Outside its crate, a match on such an enum needs a wildcard arm (Reference), so adding a variant later does not break callers. A library offers a concrete type so callers can match on it; Errors in applications covers the program side.
Features
Section titled “Features”A feature is a name that turns on optional code and optional dependencies. json = ["dep:serde_json"] makes serde_json a dependency only when json is on, and #[cfg(feature = "json")] includes mod json; and the to_json method only then. required-features = ["json"] on the dump program tells Cargo to skip it otherwise (Cargo targets). With the feature off Cargo explains what to do; with it on the program runs, and the dependency tree differs:
$ cargo run -p wordstat --bin dump -- the cat the dog error: target `dump` in package `wordstat` requires the features: `json` Consider enabling them by passing, e.g., `--features="json"` $ cargo run -q -p wordstat --features json --bin dump -- the cat the dog {"cat":1,"dog":1,"the":2} $ cargo tree -p wordstat wordstat v0.1.0 (/home/you/wordstat/crates/wordstat) $ cargo tree -p wordstat --features json wordstat v0.1.0 (/home/you/wordstat/crates/wordstat) └── serde_json v1.0.154 ├── itoa v1.0.18 ├── memchr v2.8.3 ├── serde_core v1.0.229 └── zmij v1.0.23 $ cargo test -p wordstat # timings replaced by X.XXs test count::tests::ignores_case_and_punctuation ... ok test count::tests::empty_text_is_an_error ... ok test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in X.XXs test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in X.XXs test no_words_is_a_failure ... ok test prints_the_most_common_word_first ... ok test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in X.XXs Doc-tests wordstat test crates/wordstat/src/lib.rs - (line 3) ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in X.XXs $ cargo test # the whole workspace; timings replaced by X.XXs; the doc tests can print in either order test count::tests::empty_text_is_an_error ... ok test count::tests::ignores_case_and_punctuation ... ok test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in X.XXs test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in X.XXs test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in X.XXs test no_words_is_a_failure ... ok test prints_the_most_common_word_first ... ok test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in X.XXs test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in X.XXs Doc-tests wordstat test crates/wordstat/src/lib.rs - (line 3) ... ok test crates/wordstat/src/json.rs - json::Stats::to_json (line 6) ... ok test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in X.XXs
This output changes from run to run. This is one real run.
In the last two steps, cargo test -p wordstat runs one doc test and a plain cargo test at the root runs two. wordstat-report asks for features = ["json"], and when several packages in one build use a dependency, Cargo “will use the union of all features enabled on that dependency” (Features). Building the whole workspace therefore switched json on for wordstat too. So features should only add things: the build you run may have more on than the one you wrote the code for. The API Guidelines ask for json, not use-json (C-FEATURE).
Use a gated item with the feature off and you get a plain “method not found” that does not mention the feature, so a missing features = [...] is the first thing to check:
error[E0599]: no method named `to_json` found for struct `Stats` in the current scope --> crates/wordstat/src/main.rs:9:34 | 9 | println!("{}", stats.to_json()); | ^^^^^^^ method not found in `Stats`
Lints, formatting and the minimum Rust version
Section titled “Lints, formatting and the minimum Rust version”[workspace.lints] sets lint levels once, and each member opts in with [lints] workspace = true. This workspace forbids unsafe_code, asks for documentation on public items and warns on dbg! ([lints]). clippy.toml holds settings for individual Clippy lints (here a lower argument limit), and rustfmt.toml holds formatter settings (here max_width = 80, where the default is 100) (rustfmt, Clippy). A copy of the library with three problems added (an undocumented public function, a dbg!, five arguments) shows them at work: Clippy counts 5/4 because clippy.toml lowered its limit to 4. Then the same run with missing_docs raised to deny:
$ cargo clippy --workspace
warning: this function has too many arguments (5/4)
--> crates/wordstat/src/lib.rs:18:1
|
18 | pub fn pad(a: usize, b: usize, c: usize, d: usize, e: usize) -> usize {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.99.0/index.html#too_many_arguments
= note: `#[warn(clippy::too_many_arguments)]` on by default
warning: the `dbg!` macro is intended as a debugging tool
--> crates/wordstat/src/lib.rs:19:5
|
19 | dbg!(a + b + c + d + e)
| ^^^^^^^^^^^^^^^^^^^^^^^
|
= help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.99.0/index.html#dbg_macro
= note: requested on the command line with `-W clippy::dbg-macro`
help: remove the invocation before committing it to a version control system
|
19 - dbg!(a + b + c + d + e)
19 + a + b + c + d + e
|
warning: missing documentation for a function
--> crates/wordstat/src/lib.rs:18:1
|
18 | pub fn pad(a: usize, b: usize, c: usize, d: usize, e: usize) -> usize {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: requested on the command line with `-W missing-docs`
warning: `wordstat` (lib) generated 3 warnings (run `cargo clippy --fix --lib -p wordstat -- ` to apply 1 suggestion)
warning: this function has too many arguments (5/4) --> crates/wordstat/src/lib.rs:18:1 | 18 | pub fn pad(a: usize, b: usize, c: usize, d: usize, e: usize) -> usize { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.99.0/index.html#too_many_arguments = note: `#[warn(clippy::too_many_arguments)]` on by default warning: the `dbg!` macro is intended as a debugging tool --> crates/wordstat/src/lib.rs:19:5 | 19 | dbg!(a + b + c + d + e) | ^^^^^^^^^^^^^^^^^^^^^^^ | = help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.99.0/index.html#dbg_macro = note: requested on the command line with `-W clippy::dbg-macro` help: remove the invocation before committing it to a version control system | 19 - dbg!(a + b + c + d + e) 19 + a + b + c + d + e | error: missing documentation for a function --> crates/wordstat/src/lib.rs:18:1 | 18 | pub fn pad(a: usize, b: usize, c: usize, d: usize, e: usize) -> usize { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: requested on the command line with `-D missing-docs` warning: `wordstat` (lib) generated 2 warnings error: could not compile `wordstat` (lib) due to 1 previous error; 2 warnings emitted
cargo fmt --check passes (no output) on the project:
$ cargo fmt --check
rust-version = "1.85" declares the oldest compiler the package supports (the MSRV, minimum supported Rust version). Its main effect is a version comparison: on an older compiler Cargo “will report that as an error to the user” (rust-version). It does not compile your code with the old compiler (see the CI guide). Declare a version above the installed one and Cargo refuses, naming wordstat once for each target it builds (the library and two programs):
error: rustc 1.99.1 is not supported by the following packages: [email protected] requires rustc 1.100 [email protected] requires rustc 1.100 [email protected] requires rustc 1.100 [email protected] requires rustc 1.100
Whether the declared version is true is something you test. This session installs 1.85.0 next to 1.99.1 (needs network; the image is ARM64) and runs the tests with it. They passed here, once, on this one project. With resolver = "3", Cargo prefers dependency versions that fit your rust-version (Resolver). It is the default for edition 2024, but a workspace root with no package has no edition, so the root file sets it.
$ rustup toolchain install 1.85.0 --profile minimal # needs network 1.85.0-aarch64-unknown-linux-gnu installed - rustc 1.85.0 (4d91de4e4 2025-02-17) $ cargo +1.85.0 --version cargo 1.85.0 (d73d2caf9 2024-12-31) $ cargo +1.85.0 test # timings replaced by X.XXs; the doc tests can print in either order test count::tests::empty_text_is_an_error ... ok test count::tests::ignores_case_and_punctuation ... ok test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in X.XXs test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in X.XXs test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in X.XXs test no_words_is_a_failure ... ok test prints_the_most_common_word_first ... ok test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in X.XXs test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in X.XXs Doc-tests wordstat test crates/wordstat/src/json.rs - json::Stats::to_json (line 6) ... ok test crates/wordstat/src/lib.rs - (line 3) ... ok test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in X.XXs
This output changes from run to run. This is one real run.
Documentation, doc tests and names
Section titled “Documentation, doc tests and names”//! documents the crate or module that contains it and /// the item after it. cargo doc builds the HTML, and cargo test compiles and runs the code blocks in those comments. Lines that start with # are hidden in the docs but compiled, so a final # Ok::<(), wordstat::Error>(()) lets an example use ? (rustdoc book). Timings in these sessions are masked, and the two doc tests can print in either order:
$ cargo doc --no-deps -p wordstat Documenting wordstat v0.1.0 (/home/you/wordstat/crates/wordstat) Generated target/doc/wordstat/index.html $ cargo test --doc -p wordstat --features json # timings masked; the two tests can print in either order Doc-tests wordstat running 2 tests test crates/wordstat/src/lib.rs - (line 3) ... ok test crates/wordstat/src/json.rs - json::Stats::to_json (line 6) ... ok test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in X.XXs
This output changes from run to run. This is one real run.
Names follow RFC 430: UpperCamelCase for types and traits, snake_case for modules and functions, SCREAMING_SNAKE_CASE for constants (RFC 430 says snake_case for crates; the API Guidelines call that unclear). The API Guidelines add, for example, as_ for a free borrowed view, to_ for an expensive conversion and into_ for one that consumes its input.
Cargo.lock and looking at dependencies
Section titled “Cargo.lock and looking at dependencies”The Cargo FAQ says whether to commit it “is dependent on the needs of your package”, and that the lock file “does not affect the consumers of your package, only Cargo.toml does that” (FAQ). So the lock decides what your own builds and CI use; a library’s users resolve versions from your Cargo.toml. This project commits it because it has programs. cargo tree prints the dependency graph and -i inverts it to show who depends on a package (cargo tree).
The same job in your language
Section titled “The same job in your language”The sessions below run the same split in Go (two modules, a build tag as the feature, an internal package) and in npm (two packages and an exports field). The other rows name the closest mechanism and link its documentation; they were not run.
| Language | Several packages in one repo | A part you switch on | A name kept private | Run here |
|---|---|---|---|---|
| Rust | workspace, members |
feature, cfg(feature) |
private mod, pub(crate) |
yes (above) |
| Go | go.work with use (modules reference) |
build tag: //go:build json, -tags json (build constraints) |
a lower-case name, or an internal/ directory (internal packages) |
yes |
| JavaScript (Node), TypeScript | npm workspaces (docs) |
not run | the exports field of package.json (Node docs) |
yes |
| Java | Maven modules, built together by one Maven run (guide) | not run | not run | no |
| Kotlin | Gradle multi-project build, include(...) in settings.gradle.kts (docs) |
not run | not run | no |
| C# | a solution holding projects (docs) | not run | not run | no |
| Python | not run | extras: [project.optional-dependencies] (spec) |
a leading underscore is a convention, not run (PEP 8) | no |
| C, C++ | CMake targets joined with target_link_libraries() (buildsystem manual) |
option() (docs) |
not run | no |
Go first. The program builds with the workspace, and the program that needs ToJSON fails to compile until the tag is given:
go 1.24
use ( ./wordstat ./app)// Package wordstat counts the words in a piece of text.package wordstat
import ( "errors" "strings"
"example.com/wordstat/internal/words")
// ErrEmpty is returned when the text has no words.var ErrEmpty = errors.New("no words to count")
// Count returns how often each word appears.func Count(text string) (map[string]int, error) { counts := map[string]int{} for _, w := range strings.Fields(text) { counts[words.Normalize(w)]++ } if len(counts) == 0 { return nil, ErrEmpty } return counts, nil}//go:build json
package wordstat
import "encoding/json"
// ToJSON is only compiled with `-tags json`.func ToJSON(counts map[string]int) string { b, _ := json.Marshal(counts) return string(b)}package main
import ( "fmt" "os" "strings"
"example.com/wordstat")
func main() { counts, err := wordstat.Count(strings.Join(os.Args[1:], " ")) if err != nil { fmt.Fprintln(os.Stderr, "dump:", err) os.Exit(1) } fmt.Println(wordstat.ToJSON(counts))}4 different words$ go run example.com/app/cmd/dump the cat the dog
# example.com/app/cmd/dump app/cmd/dump/main.go:17:23: undefined: wordstat.ToJSON$ go run -tags json example.com/app/cmd/dump the cat the dog
{"cat":1,"dog":1,"the":2}Go’s compiler enforces internal: a package outside wordstat that imports wordstat/internal/words is refused:
package example.com/app/peek app/peek/main.go:6:2: use of internal package example.com/wordstat/internal/words not allowed
Two differences from Cargo, both visible above. A Go build tag switches files in a package; it does not make a dependency optional, and encoding/json was in the standard library all along. And Go’s internal rule is about directories, where Rust’s privacy follows mod and pub items. npm’s exports is closer to Rust’s private modules: only the listed entry points can be imported.
{ "name": "wordstat-workspace", "private": true, "workspaces": ["packages/*"]}{ "name": "wordstat", "version": "0.1.0", "type": "module", "exports": { ".": "./index.js" }}import { count } from "wordstat";
console.log(count("the cat and the dog"));$ npm install $ npm ls wordstat-workspace@ /home/you/wordstat/npm ├─┬ [email protected] -> ./packages/app │ └── [email protected] deduped -> ./packages/wordstat └── [email protected] -> ./packages/wordstat $ node packages/app/index.js { the: 2, cat: 1, and: 1, dog: 1 } $ node packages/app/peek.js Error [ERR_PACKAGE_PATH_NOT_EXPORTED]: Package subpath './normalize.js' is not defined by "exports" in /home/you/wordstat/npm/node_modules/wordstat/package.json imported from /home/you/wordstat/npm/packages/app/peek.js
Try it yourself
Section titled “Try it yourself”Run each in the project. The session ran tasks 1 and 2; task 3 is the deny run above.
cargo build -p wordstat --features nope. Cargo refuses a feature the package does not define, and says so.cargo tree --workspace -i serde_json. The tree reads fromserde_jsonupward:wordstatneeds it, andwordstat-reportneedswordstat.- Add
pub fn pad(a: usize, b: usize, c: usize, d: usize, e: usize) -> usize { dbg!(a + b + c + d + e) }tocrates/wordstat/src/lib.rs, changemissing_docs = "warn"to"deny"in the rootCargo.toml, and runcargo clippy --workspace. The missing-docs warning becomes an error and the build stops.
$ cargo build -p wordstat --features nope error: the package 'wordstat' does not contain this feature: nope $ cargo tree --workspace -i serde_json serde_json v1.0.154 └── wordstat v0.1.0 (/home/you/wordstat/crates/wordstat) └── wordstat-report v0.1.0 (/home/you/wordstat/crates/wordstat-report)
Read more
Section titled “Read more”- The Cargo Book: Workspaces, Features, Rust version; the Book’s Cargo Workspaces
- Rust API Guidelines and rustdoc: documentation tests
- In this course: Modules, Testing, Errors in applications, Finding and using crates
Versions on this page: Rust 1.99.1, with 1.85.0 for the MSRV check, and serde_json as pinned in Cargo.lock. Go and Node are the versions in the runner image.
Learn Rust is an independent project, not affiliated with the Rust Project or the Rust Foundation. Code and text are under the MIT licence.Source on GitHub.