Book Image

Practical System Programming for Rust Developers

By : Prabhu Eshwarla
Book Image

Practical System Programming for Rust Developers

By: Prabhu Eshwarla

Overview of this book

Modern programming languages such as Python, JavaScript, and Java have become increasingly accepted for application-level programming, but for systems programming, C and C++ are predominantly used due to the need for low-level control of system resources. Rust promises the best of both worlds: the type safety of Java, and the speed and expressiveness of C++, while also including memory safety without a garbage collector. This book is a comprehensive introduction if you’re new to Rust and systems programming and are looking to build reliable and efficient systems software without C or C++. The book takes a unique approach by starting each topic with Linux kernel concepts and APIs relevant to that topic. You’ll also explore how system resources can be controlled from Rust. As you progress, you’ll delve into advanced topics. You’ll cover network programming, focusing on aspects such as working with low-level network primitives and protocols in Rust, before going on to learn how to use and compile Rust with WebAssembly. Later chapters will take you through practical code examples and projects to help you build on your knowledge. By the end of this Rust programming book, you will be equipped with practical skills to write systems software tools, libraries, and utilities in Rust.
Table of Contents (17 chapters)
1
Section 1: Getting Started with System Programming in Rust
6
Section 2: Managing and Controlling System Resources in Rust
12
Section 3: Advanced Topics

Automating dependency management

You learned in the previous section how Cargo can be used to set up the base project directory structure and scaffolding for a new project, and how to build various types of binary and library crates. We will look at the dependency management features of Cargo in this section.

Rust comes with a built-in standard library consisting of language primitives and commonly used functions, but it is small by design (compared to other languages). Most real-world programs in Rust depend on additional external libraries to improve functionality and developer productivity. Any such external code that is used is a dependency for the program. Cargo makes it easy to specify and manage dependencies.

In the Rust ecosystem, crates.io is the central public package registry for discovering and downloading libraries (called packages or crates in Rust). It is similar to npm in the JavaScript world. Cargo uses crates.io as the default package registry.

Dependencies are specified in the [dependencies] section of Cargo.toml. Let's see an example.

Start a new project with this command:

cargo new deps-example && cd deps-example

In Cargo.toml, make the following entry to include an external library:

[dependencies]  
chrono = "0.4.0"

Chrono is a datetime library. This is called a dependency because our deps-example crate depends on this external library for its functionality.

When you run cargo build, cargo looks for a crate on crates.io with this name and version. If found, it downloads this crate along with all of its dependencies, compiles them all, and updates a file called Cargo.lock with the exact versions of packages downloaded. The Cargo.lock file is a generated file and not meant for editing.

Each dependency in Cargo.toml is specified in a new line and takes the format <crate-name> = "<semantic-version-number>". Semantic versioning or Semver has the form X.Y.Z, where X is the major version number, Y is the minor version, and Z is the patch version.

Specifying the location of a dependency

There are many ways to specify the location and version of dependencies in Cargo.toml, some of which are summarized here:

  • Crates.io registry: This is the default option and all that is needed is to specify the package name and version string as we did earlier in this section.
  • Alternative registry: While crates.io is the default registry, Cargo provides the option to use an alternate registry. The registry name has to be configured in the .cargo/config file, and in Cargo.toml, an entry is to be made with the registry name, as shown in the example here:
    [dependencies]     
     cratename = { version = "2.1", registry = "alternate-
         registry-name" }
  • Git repository: A Git repo can be specified as the dependency. Here is how to do it:
    [dependencies]
    chrono = { git = "https://github.com/chronotope/chrono" , 
        branch = "master" }

    Cargo will get the repo at the branch and location specified, and look for its Cargo.toml file in order to fetch its dependencies.

  • Specify a local path: Cargo supports path dependencies, which means the library can be a sub-crate within the main cargo package. While building the main cargo package, the sub-crates that have also been specified as dependencies will be built. But dependencies with only a path dependency cannot be uploaded to the crates.io public registry.
  • Multiple locations: Cargo supports the option to specify both a registry version and either a Git or path location. For local builds, the Git or path version is used, and the registry version will be used when the package is published to crates.io.

Using dependent packages in source code

Once the dependencies are specified in the Cargo.toml file in any of the preceding formats, we can use the external library in the package code as shown in the following example. Add the following code to src/main.rs:

use chrono::Utc;
fn main() {
    println!("Hello, time now is {:?}", Utc::now());
}

The use statement tells the compiler to bring the chrono package Utc module into the scope of this program. We can then access the function now() from the Utc module to print out the current date and time. The use statement is not mandatory. An alternative way to print datetime would be as follows:

fn main() {
    println!("Hello, time now is {:?}", chrono::Utc::now());
}

This would give the same result. But if you have to use functions from the chrono package multiple times in code, it is more convenient to bring chrono and required modules into scope once using the use statement, and it becomes easier to type.

It is also possible to rename the imported package with the as keyword:

use chrono as time;
fn main() {
    println!("Hello, time now is {:?}", time::Utc::now());
}

For more details on managing dependencies, refer to the Cargo docs: https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html.

In this section, we have seen how to add dependencies to a package. Any number of dependencies can be added to Cargo.toml and used within the program. Cargo makes the dependency management process quite a pleasant experience.

Let's now look at another useful feature of Cargo – running automated tests.