Search icon CANCEL
Subscription
0
Cart icon
Your Cart (0 item)
Close icon
You have no products in your basket yet
Save more on your purchases now! discount-offer-chevron-icon
Savings automatically calculated. No voucher code required.
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Conferences
Free Learning
Arrow right icon
Arrow up icon
GO TO TOP
Practical System Programming for Rust Developers

You're reading from   Practical System Programming for Rust Developers Build fast and secure software for Linux/Unix systems with the help of practical examples

Arrow left icon
Product type Paperback
Published in Dec 2020
Publisher Packt
ISBN-13 9781800560963
Length 388 pages
Edition 1st Edition
Languages
Tools
Arrow right icon
Author (1):
Arrow left icon
Prabhu Eshwarla Prabhu Eshwarla
Author Profile Icon Prabhu Eshwarla
Prabhu Eshwarla
Arrow right icon
View More author details
Toc

Table of Contents (17) Chapters Close

Preface 1. Section 1: Getting Started with System Programming in Rust
2. Chapter 1: Tools of the Trade – Rust Toolchains and Project Structures FREE CHAPTER 3. Chapter 2: A Tour of the Rust Programming Language 4. Chapter 3: Introduction to the Rust Standard Library 5. Chapter 4: Managing Environment, Command Line, and Time 6. Section 2: Managing and Controlling System Resources in Rust
7. Chapter 5: Memory Management in Rust 8. Chapter 6: Working with Files and Directories in Rust 9. Chapter 7: Implementing Terminal I/O in Rust 10. Chapter 8: Working with Processes and Signals 11. Chapter 9: Managing Concurrency 12. Section 3: Advanced Topics
13. Chapter 10: Working with Device I/O 14. Chapter 11: Learning Network Programming 15. Chapter 12: Writing Unsafe Rust and FFI 16. Other Books You May Enjoy

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.

  1. The first step is to generate a Rust source package using the cargo new command.
  2. 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.

  3. Once the command executes, you will see the following directory structure:

    Figure 1.2 – Directory structure

    Figure 1.2 – 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 called main.rs:

    fn main() {
        println!("Hello, world!");
    }
  4. 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 location target/debug.

  5. 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"
  6. 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 the target/debug folder. You will see Hello, world! printed to your console.

  7. 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"
  8. Next, create a new file, src/second.rs, and add the following code:
    fn main() {
        println!("Hello, for the second time!");
    }
  9. 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 the example directory can be built into executable binaries using this target.
  • [[test]]: Files located in the tests 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: The cargo build command uses the dev profile by default. Packages built with this option are optimized for compile-time speed.
  • release: The cargo build –-release command enables the release profile, which is suitable for production release, and is optimized for runtime speed.
  • test: The cargo test command uses this profile. This is used to build test executables.
  • bench: The cargo 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.

You have been reading a chapter from
Practical System Programming for Rust Developers
Published in: Dec 2020
Publisher: Packt
ISBN-13: 9781800560963
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at $19.99/month. Cancel anytime