Files
docs-rust/ch14/ch14-03-cargo-workspaces.html
2026-06-22 21:27:36 +05:30

331 lines
16 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Cargo Workspaces</title>
</head>
<body>
<h2 id="cargo-workspaces"><a class="header" href="#cargo-workspaces">Cargo Workspaces</a></h2>
<p>In Chapter 12, we built a package that included a binary crate and a library
crate. As your project develops, you might find that the library crate
continues to get bigger and you want to split your package further into
multiple library crates. Cargo offers a feature called <em>workspaces</em> that can
help manage multiple related packages that are developed in tandem.</p>
<h3 id="creating-a-workspace"><a class="header" href="#creating-a-workspace">Creating a Workspace</a></h3>
<p>A <em>workspace</em> is a set of packages that share the same <em>Cargo.lock</em> and output
directory. Lets make a project using a workspace—well use trivial code so
that we can concentrate on the structure of the workspace. There are multiple
ways to structure a workspace, so well just show one common way. Well have a
workspace containing a binary and two libraries. The binary, which will provide
the main functionality, will depend on the two libraries. One library will
provide an <code>add_one</code> function and the other library an <code>add_two</code> function.
These three crates will be part of the same workspace. Well start by creating
a new directory for the workspace:</p>
<pre><code class="language-console">$ mkdir add
$ cd add
</code></pre>
<p>Next, in the <em>add</em> directory, we create the <em>Cargo.toml</em> file that will
configure the entire workspace. This file wont have a <code>[package]</code> section.
Instead, it will start with a <code>[workspace]</code> section that will allow us to add
members to the workspace. We also make a point to use the latest and greatest
version of Cargos resolver algorithm in our workspace by setting the
<code>resolver</code> value to <code>"3"</code>:</p>
<p><span class="filename">Filename: Cargo.toml</span></p>
<pre><code class="language-toml">[workspace]
resolver = "3"
</code></pre>
<p>Next, well create the <code>adder</code> binary crate by running <code>cargo new</code> within the
<em>add</em> directory:</p>
<!-- manual-regeneration
cd listings/ch14-more-about-cargo/output-only-01-adder-crate/add
remove `members = ["adder"]` from Cargo.toml
rm -rf adder
cargo new adder
copy output below
-->
<pre><code class="language-console">$ cargo new adder
Created binary (application) `adder` package
Adding `adder` as member of workspace at `file:///projects/add`
</code></pre>
<p>Running <code>cargo new</code> inside a workspace also automatically adds the newly created
package to the <code>members</code> key in the <code>[workspace]</code> definition in the workspace
<em>Cargo.toml</em>, like this:</p>
<pre><code class="language-toml">[workspace]
resolver = "3"
members = ["adder"]
</code></pre>
<p>At this point, we can build the workspace by running <code>cargo build</code>. The files
in your <em>add</em> directory should look like this:</p>
<pre><code class="language-text">├── Cargo.lock
├── Cargo.toml
├── adder
│ ├── Cargo.toml
│ └── src
│ └── main.rs
└── target
</code></pre>
<p>The workspace has one <em>target</em> directory at the top level that the compiled
artifacts will be placed into; the <code>adder</code> package doesnt have its own
<em>target</em> directory. Even if we were to run <code>cargo build</code> from inside the
<em>adder</em> directory, the compiled artifacts would still end up in <em>add/target</em>
rather than <em>add/adder/target</em>. Cargo structures the <em>target</em> directory in a
workspace like this because the crates in a workspace are meant to depend on
each other. If each crate had its own <em>target</em> directory, each crate would have
to recompile each of the other crates in the workspace to place the artifacts
in its own <em>target</em> directory. By sharing one <em>target</em> directory, the crates
can avoid unnecessary rebuilding.</p>
<h3 id="creating-the-second-package-in-the-workspace"><a class="header" href="#creating-the-second-package-in-the-workspace">Creating the Second Package in the Workspace</a></h3>
<p>Next, lets create another member package in the workspace and call it
<code>add_one</code>. Generate a new library crate named <code>add_one</code>:</p>
<!-- manual-regeneration
cd listings/ch14-more-about-cargo/output-only-02-add-one/add
remove `"add_one"` from `members` list in Cargo.toml
rm -rf add_one
cargo new add_one --lib
copy output below
-->
<pre><code class="language-console">$ cargo new add_one --lib
Created library `add_one` package
Adding `add_one` as member of workspace at `file:///projects/add`
</code></pre>
<p>The top-level <em>Cargo.toml</em> will now include the <em>add_one</em> path in the <code>members</code>
list:</p>
<p><span class="filename">Filename: Cargo.toml</span></p>
<pre><code class="language-toml">[workspace]
resolver = "3"
members = ["adder", "add_one"]
</code></pre>
<p>Your <em>add</em> directory should now have these directories and files:</p>
<pre><code class="language-text">├── Cargo.lock
├── Cargo.toml
├── add_one
│ ├── Cargo.toml
│ └── src
│ └── lib.rs
├── adder
│ ├── Cargo.toml
│ └── src
│ └── main.rs
└── target
</code></pre>
<p>In the <em>add_one/src/lib.rs</em> file, lets add an <code>add_one</code> function:</p>
<p><span class="filename">Filename: add_one/src/lib.rs</span></p>
<pre><code class="language-rust noplayground">pub fn add_one(x: i32) -&gt; i32 {
x + 1
}</code></pre>
<p>Now we can have the <code>adder</code> package with our binary depend on the <code>add_one</code>
package that has our library. First, well need to add a path dependency on
<code>add_one</code> to <em>adder/Cargo.toml</em>.</p>
<p><span class="filename">Filename: adder/Cargo.toml</span></p>
<pre><code class="language-toml">[dependencies]
add_one = { path = "../add_one" }
</code></pre>
<p>Cargo doesnt assume that crates in a workspace will depend on each other, so
we need to be explicit about the dependency relationships.</p>
<p>Next, lets use the <code>add_one</code> function (from the <code>add_one</code> crate) in the
<code>adder</code> crate. Open the <em>adder/src/main.rs</em> file and change the <code>main</code>
function to call the <code>add_one</code> function, as in Listing 14-7.</p>
<figure class="listing" id="listing-14-7">
<span class="file-name">Filename: adder/src/main.rs</span>
<pre><code class="language-rust ignore">fn main() {
let num = 10;
println!("Hello, world! {num} plus one is {}!", add_one::add_one(num));
}</code></pre>
<figcaption><a href="#listing-14-7">Listing 14-7</a>: Using the <code>add_one</code> library crate from the <code>adder</code> crate</figcaption>
</figure>
<p>Lets build the workspace by running <code>cargo build</code> in the top-level <em>add</em>
directory!</p>
<!-- manual-regeneration
cd listings/ch14-more-about-cargo/listing-14-07/add
cargo build
copy output below; the output updating script doesn't handle subdirectories in paths properly
-->
<pre><code class="language-console">$ cargo build
Compiling add_one v0.1.0 (file:///projects/add/add_one)
Compiling adder v0.1.0 (file:///projects/add/adder)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.22s
</code></pre>
<p>To run the binary crate from the <em>add</em> directory, we can specify which package
in the workspace we want to run by using the <code>-p</code> argument and the package name
with <code>cargo run</code>:</p>
<!-- manual-regeneration
cd listings/ch14-more-about-cargo/listing-14-07/add
cargo run -p adder
copy output below; the output updating script doesn't handle subdirectories in paths properly
-->
<pre><code class="language-console">$ cargo run -p adder
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.00s
Running `target/debug/adder`
Hello, world! 10 plus one is 11!
</code></pre>
<p>This runs the code in <em>adder/src/main.rs</em>, which depends on the <code>add_one</code> crate.</p>
<!-- Old headings. Do not remove or links may break. -->
<p><a id="depending-on-an-external-package-in-a-workspace"></a></p>
<h3 id="depending-on-an-external-package"><a class="header" href="#depending-on-an-external-package">Depending on an External Package</a></h3>
<p>Notice that the workspace has only one <em>Cargo.lock</em> file at the top level,
rather than having a <em>Cargo.lock</em> in each crates directory. This ensures that
all crates are using the same version of all dependencies. If we add the <code>rand</code>
package to the <em>adder/Cargo.toml</em> and <em>add_one/Cargo.toml</em> files, Cargo will
resolve both of those to one version of <code>rand</code> and record that in the one
<em>Cargo.lock</em>. Making all crates in the workspace use the same dependencies
means the crates will always be compatible with each other. Lets add the
<code>rand</code> crate to the <code>[dependencies]</code> section in the <em>add_one/Cargo.toml</em> file
so that we can use the <code>rand</code> crate in the <code>add_one</code> crate:</p>
<!-- When updating the version of `rand` used, also update the version of
`rand` used in these files so they all match:
* ch02-00-guessing-game-tutorial.md
* ch07-04-bringing-paths-into-scope-with-the-use-keyword.md
-->
<p><span class="filename">Filename: add_one/Cargo.toml</span></p>
<pre><code class="language-toml">[dependencies]
rand = "0.8.5"
</code></pre>
<p>We can now add <code>use rand;</code> to the <em>add_one/src/lib.rs</em> file, and building the
whole workspace by running <code>cargo build</code> in the <em>add</em> directory will bring in
and compile the <code>rand</code> crate. We will get one warning because we arent
referring to the <code>rand</code> we brought into scope:</p>
<!-- manual-regeneration
cd listings/ch14-more-about-cargo/no-listing-03-workspace-with-external-dependency/add
cargo build
copy output below; the output updating script doesn't handle subdirectories in paths properly
-->
<pre><code class="language-console">$ cargo build
Updating crates.io index
Downloaded rand v0.8.5
--snip--
Compiling rand v0.8.5
Compiling add_one v0.1.0 (file:///projects/add/add_one)
warning: unused import: `rand`
--&gt; add_one/src/lib.rs:1:5
|
1 | use rand;
| ^^^^
|
= note: `#[warn(unused_imports)]` on by default
warning: `add_one` (lib) generated 1 warning (run `cargo fix --lib -p add_one` to apply 1 suggestion)
Compiling adder v0.1.0 (file:///projects/add/adder)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.95s
</code></pre>
<p>The top-level <em>Cargo.lock</em> now contains information about the dependency of
<code>add_one</code> on <code>rand</code>. However, even though <code>rand</code> is used somewhere in the
workspace, we cant use it in other crates in the workspace unless we add
<code>rand</code> to their <em>Cargo.toml</em> files as well. For example, if we add <code>use rand;</code>
to the <em>adder/src/main.rs</em> file for the <code>adder</code> package, well get an error:</p>
<!-- manual-regeneration
cd listings/ch14-more-about-cargo/output-only-03-use-rand/add
cargo build
copy output below; the output updating script doesn't handle subdirectories in paths properly
-->
<pre><code class="language-console">$ cargo build
--snip--
Compiling adder v0.1.0 (file:///projects/add/adder)
error[E0432]: unresolved import `rand`
--&gt; adder/src/main.rs:2:5
|
2 | use rand;
| ^^^^ no external crate `rand`
</code></pre>
<p>To fix this, edit the <em>Cargo.toml</em> file for the <code>adder</code> package and indicate
that <code>rand</code> is a dependency for it as well. Building the <code>adder</code> package will
add <code>rand</code> to the list of dependencies for <code>adder</code> in <em>Cargo.lock</em>, but no
additional copies of <code>rand</code> will be downloaded. Cargo will ensure that every
crate in every package in the workspace using the <code>rand</code> package will use the
same version as long as they specify compatible versions of <code>rand</code>, saving us
space and ensuring that the crates in the workspace will be compatible with
each other.</p>
<p>If crates in the workspace specify incompatible versions of the same
dependency, Cargo will resolve each of them but will still try to resolve as
few versions as possible.</p>
<h3 id="adding-a-test-to-a-workspace"><a class="header" href="#adding-a-test-to-a-workspace">Adding a Test to a Workspace</a></h3>
<p>For another enhancement, lets add a test of the <code>add_one::add_one</code> function
within the <code>add_one</code> crate:</p>
<p><span class="filename">Filename: add_one/src/lib.rs</span></p>
<pre><code class="language-rust noplayground">pub fn add_one(x: i32) -&gt; i32 {
x + 1
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
assert_eq!(3, add_one(2));
}
}</code></pre>
<p>Now run <code>cargo test</code> in the top-level <em>add</em> directory. Running <code>cargo test</code> in
a workspace structured like this one will run the tests for all the crates in
the workspace:</p>
<!-- manual-regeneration
cd listings/ch14-more-about-cargo/no-listing-04-workspace-with-tests/add
cargo test
copy output below; the output updating script doesn't handle subdirectories in
paths properly
-->
<pre><code class="language-console">$ cargo test
Compiling add_one v0.1.0 (file:///projects/add/add_one)
Compiling adder v0.1.0 (file:///projects/add/adder)
Finished `test` profile [unoptimized + debuginfo] target(s) in 0.20s
Running unittests src/lib.rs (target/debug/deps/add_one-93c49ee75dc46543)
running 1 test
test tests::it_works ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Running unittests src/main.rs (target/debug/deps/adder-3a47283c568d2b6a)
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Doc-tests add_one
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
</code></pre>
<p>The first section of the output shows that the <code>it_works</code> test in the <code>add_one</code>
crate passed. The next section shows that zero tests were found in the <code>adder</code>
crate, and then the last section shows that zero documentation tests were found
in the <code>add_one</code> crate.</p>
<p>We can also run tests for one particular crate in a workspace from the
top-level directory by using the <code>-p</code> flag and specifying the name of the crate
we want to test:</p>
<!-- manual-regeneration
cd listings/ch14-more-about-cargo/no-listing-04-workspace-with-tests/add
cargo test -p add_one
copy output below; the output updating script doesn't handle subdirectories in paths properly
-->
<pre><code class="language-console">$ cargo test -p add_one
Finished `test` profile [unoptimized + debuginfo] target(s) in 0.00s
Running unittests src/lib.rs (target/debug/deps/add_one-93c49ee75dc46543)
running 1 test
test tests::it_works ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Doc-tests add_one
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
</code></pre>
<p>This output shows <code>cargo test</code> only ran the tests for the <code>add_one</code> crate and
didnt run the <code>adder</code> crate tests.</p>
<p>If you publish the crates in the workspace to
<a href="https://crates.io/">crates.io</a><!-- ignore -->, each crate in the workspace
will need to be published separately. Like <code>cargo test</code>, we can publish a
particular crate in our workspace by using the <code>-p</code> flag and specifying the
name of the crate we want to publish.</p>
<p>For additional practice, add an <code>add_two</code> crate to this workspace in a similar
way as the <code>add_one</code> crate!</p>
<p>As your project grows, consider using a workspace: It enables you to work with
smaller, easier-to-understand components than one big blob of code.
Furthermore, keeping the crates in a workspace can make coordination between
crates easier if they are often changed at the same time.</p>
</body>
</html>