Automating build management with Cargo
When Rust code is compiled and built, the generated binary can either be a standalone executable binary or a library that can be used by other projects. In this section, we will look at how Cargo can be used to create Rust binaries and libraries, and how to configure metadata in Cargo.toml
to provide build instructions.
Building a basic binary crate
In this section, we will build a basic binary crate. A binary crate when built, produces an executable binary file. This is the default crate type for the cargo tool. Let's now look at the command to create a binary crate.
- The first step is to generate a Rust source package using the
cargo new
command. - Run the following command in a terminal session inside your working directory to create a new package:
cargo new --bin first-program && cd first-program
The
--bin
flag is to tell Cargo to generate a package that, when compiled, would produce a binary crate (executable).first-program
is the name of the package given. You can specify a name of your choice. - Once the command executes, you will see the following directory structure:
The
Cargo.toml
file contains the metadata for the package:[package] name = "first-program" version = "0.1.0" authors = [<your email>] edition = "2018"
And the
src
directory contains one file calledmain.rs
:fn main() { println!("Hello, world!"); }
- To generate a binary crate (or executable) from this package, run the following command:
cargo build
This command creates a folder called
target
in the project root and creates a binary crate (executable) with the same name as the package name (first-program
, in our case) in the locationtarget/debug
. - Execute the following from the command line:
cargo run
You will see the following printed to your console:
Hello, world!
Note on path setting to execute binaries
Note that
LD_LIBRARY_PATH
should be set to include the toolchain library in the path. Execute the following command for Unix-like platforms. If your executable fails with the error Image not found, for Windows, alter the syntax suitably:export LD_LIBRARY_PATH=$(rustc --print sysroot)/lib:$LD_LIBRARY_PATH
Alternatively, you can build and run code with one command –
cargo run
, which is convenient for development purposes.By default, the name of the binary crate (executable) generated is the same as the name of the source package. If you wish to change the name of the binary crate, add the following lines to
Cargo.toml
:[[bin]] name = "new-first-program" path = "src/main.rs"
- Run the following in the command line:
cargo run –-bin new-first-program
You will see a new executable with the name
new-first-program
in thetarget/debug
folder. You will see Hello, world! printed to your console. - A cargo package can contain the source for multiple binaries. Let's learn how to add another binary to our project. In
Cargo.toml
, add a new[[bin]]
target below the first one:[[bin]] name = "new-first-program" path = "src/main.rs" [[bin]] name = "new-second-program" path = "src/second.rs"
- Next, create a new file,
src/second.rs
, and add the following code:fn main() { println!("Hello, for the second time!"); }
- Run the following:
cargo run --bin new-second-program
You will see the statement Hello, for the second time! printed to your console. You'll also find a new executable created in the target/debug
directory with the name new-second-program
.
Congratulations! You have learned how to do the following:
- Create your first Rust source package and compile it into an executable binary crate
- Give a new name to the binary, different from the package name
- Add a second binary to the same cargo package
Note that a cargo
package can contain one or more binary crates.
Configuring Cargo
A cargo package has an associated Cargo.toml
file, which is also called the manifest.
The manifest, at a minimum, contains the [package]
section but can contain many other sections. A subset of the sections are listed here:
Specifying output targets for the package: Cargo packages can have five types of targets:
[[bin]]
: A binary target is an executable program that can be run after it is built.[lib]
: A library target produces a library that can be used by other libraries and executables.[[example]]
: This target is useful for libraries to demonstrate the use of external APIs to users through example code. The example source code located in theexample
directory can be built into executable binaries using this target.[[test]]
: Files located in thetests
directory represent integration tests and each of these can be compiled into a separate executable binary.[[bench]]
: Benchmark functions defined in libraries and binaries are compiled into separate executables.
For each of these targets, the configuration can be specified, including parameters such as the name of the target, the source file of the target, and whether you want cargo to automatically run test scripts and generate documentation for the target. You may recall that in the previous section, we changed the name and set the source file for the generated binary executable.
Specifying dependencies for the package: The source files in a package may depend on other internal or external libraries, which are also called dependencies. Each of these in turn may depend on other libraries and so on. Cargo downloads the list of dependencies specified under this section and links them to the final output targets. The various types of dependencies include the following:
[dependencies]
: Package library or binary dependencies[dev-dependencies]
: Dependencies for examples, tests, and benchmarks[build-dependencies]
: Dependencies for build scripts (if any are specified)[target]
: This is for the cross-compilation of code for various target architectures. Note that this is not to be confused with the output targets of the package, which can be lib, bin, and so on.
Specifying build profiles: There are four types of profiles that can be specified while building a cargo package:
dev
: Thecargo build
command uses thedev
profile by default. Packages built with this option are optimized for compile-time speed.release
: Thecargo build –-release
command enables the release profile, which is suitable for production release, and is optimized for runtime speed.test
: Thecargo test
command uses this profile. This is used to build test executables.bench
: Thecargo bench
command creates the benchmark executable, which automatically runs all functions annotated with the#[bench]
attribute.
Specifying the package as a workspace: A workspace is a unit of organization where multiple packages can be grouped together into a project and is useful to save disk space and compilation time when there are shared dependencies across a set of related packages. The [workspace]
section can be used to define the list of packages that are part of the workspace.
Building a static library crate
We have seen how to create binary crates. Let's now learn how to create a library crate:
cargo new --lib my-first-lib
The default directory structure of a new cargo project is as follows:
├── Cargo.toml ├── src │ └── lib.rs
Add the following code in src/lib.rs
:
pub fn hello_from_lib(message: &str) { println!("Printing Hello {} from library",message); }
Run the following:
cargo build
You will see the library built under target/debug
and it will have the name libmy_first_lib.rlib
.
To invoke the function in this library, let's build a small binary crate. Create a bin
directory under src
, and a new file, src/bin/mymain.rs
.
Add the following code:
use my_first_lib::hello_from_lib; fn main() { println!("Going to call library function"); hello_from_lib("Rust system programmer"); }
The use my_first_lib::hello_from_lib
statement tells the compiler to bring the library function into the scope of this program.
Run the following:
cargo run --bin mymain
You will see the print
statement in your console. Also, the binary mymain
will be placed in the target/debug
folder along with the library we wrote earlier. The binary crate looks for the library in the same folder, which it finds in this case. Hence it is able to invoke the function within the library.
If you want to place the mymain.rs
file in another location (instead of within src/bin
), then add a target in Cargo.toml
and mention the name and path of the binary as shown in the following example, and move the mymain.rs
file to the specified location:
[[bin]] name = "mymain" path = "src/mymain.rs"
Run cargo run --bin mymain
and you will see the println
output in your console.