官术网_书友最值得收藏!

Exploring some utility crates

Before moving on to looking at how to use the most complex crates, let's take a look at some basic Rust crates. These are not a part of the standard library, but they are useful in many different kinds of projects. They should be known by all Rust developers since they are of general applicability.

Pseudo-random number generators – the rand crate

The ability to generate pseudo-random numbers is needed for several kinds of applications, especially for games. The rand crate is rather complex, but its basic usage is shown in the following example (named use_rand): 

// Declare basic functions for pseudo-random number generators.
use rand::prelude::*;

fn main() {
// Create a pseudo-Random Number Generator for the current thread
let mut rng = thread_rng();

// Print an integer number
// between 0 (included) and 20 (excluded).
println!("{}", rng.gen_range(0, 20));

// Print a floating-point number
// between 0 (included) and 1 (excluded).
println!("{}", rng.gen::<f64>());

// Generate a Boolean.
println!("{}", if rng.gen() { "Heads" } else { "Tails" });
}

First, you create a pseudo-random number generator object. Then, you call several methods on this object. Any generator must be mutable because any generation modifies the state of the generator.

The gen_range method generates an integer number in a right-open range. The gen generic method generates a number of the specified type. Sometimes, this type can be inferred, like in the last statement, where a Boolean is expected. If the generated type is a floating-point number, it is between 0 and 1, with 1 excluded.

Logging – the log crate

For any kind of software, in particular for servers, the ability to emit logging messages is essential. The logging architecture has two components:

  • API: Defined by the log crate
  • Implementation: Defined by several possible crates

Here, an example using the popular env_logger crate is shown. If you want to emit logging messages from a library, you should only add the API crate as a dependency, as it is the responsibility of the application to define the logging implementation crate.

In the following example (named use_env_logger), we are showing an application (not a library), and so we need both crates:

#[macro_use]
extern crate log;

fn main() {
env_logger::init();
error!("Error message");
warn!("Warning message");
info!("Information message");
debug!("Debugging message");
}

In a Unix-like console, after having run cargo build, execute the following command:

RUST_LOG=debug ./target/debug/use_env_logger

It will print something like the following:

[2020-01-11T15:43:44Z ERROR logging] Error message
[2020-01-11T15:43:44Z WARN logging] Warning message
[2020-01-11T15:43:44Z INFO logging] Information message
[2020-01-11T15:43:44Z DEBUG logging] Debugging message

By typing RUST_LOG=debug at the beginning of the command, you defined the temporary environment variable RUST_LOG, with debug as its value. The debug level is the highest, and hence all logging statements are performed. Instead, if you execute the following command, only the first three lines will be printed, as the info level is not detailed enough to print debug messages:

RUST_LOG=info ./target/debug/use_env_logger

Similarly, if you execute the following command, only the first two lines will be printed, as the warn level is not detailed enough to print either the debug or the info messages:

RUST_LOG=warn ./target/debug/use_env_logger

If you execute one or the other of the following commands, only the first line will be printed, as the default logging level is error:

  • RUST_LOG=error ./target/debug/use_env_logger
  • ./target/debug/use_env_logger

Initializing static variables at runtime – the lazy_static crate

It's well known that Rust does not allow mutable static variables in safe code. Immutable static variables are allowed in safe code, but they must be initialized by constant expressions, possibly by invoking const fn functions. However, the compiler must be able to evaluate the initialization expression of any static variable.

Sometimes, however, there is a need to initialize a static variable at runtime, because the initial value depends on an input, such as a command-line argument or a configuration option. In addition, if the initialization of a variable takes a long time, instead of initializing it at the start of the program, it may be better to initialize it only the first time the variable is used. This technique is called lazy initialization.

There is a small crate, named lazy_static, that contains only one macro, which has the same name as the crate. This can be used to solve the issue mentioned previously. Its use is shown in the following project (named use_lazy_static):

use lazy_static::lazy_static;
use std::collections::HashMap;

lazy_static! {
static ref DICTIONARY: HashMap<u32, &'static str> = {
let mut m = HashMap::new();
m.insert(11, "foo");
m.insert(12, "bar");
println!("Initialized");
m
};
}

fn main() {
println!("Started");
println!("DICTIONARY contains {:?}", *DICTIONARY);
println!("DICTIONARY contains {:?}", *DICTIONARY);
}

This will print the following output:

Started
Initialized
DICTIONARY contains {12: "bar", 11: "foo"}
DICTIONARY contains {12: "bar", 11: "foo"}

As you can see, the main function starts first. Then, it tries to access the DICTIONARY static variable, and that access causes the initialization of variables. The initialized value, which is a reference, is then dereferenced and printed.

The last statement, which is identical to the previous one, does not perform the initialization again, as you can see by the fact that the Initialized text is not printed again.

Parsing the command line – the structopt crate

The command-line arguments of any program are easily accessible through the std::env::args() iterator. However, the code that parses these arguments is actually rather cumbersome. To get more maintainable code, the structopt crate can be used, as shown in the following project (named use_structopt):

use std::path::PathBuf;
use structopt::StructOpt;

#[derive(StructOpt, Debug)]
struct Opt {
/// Activate verbose mode
#[structopt(short = "v", long = "verbose")]
verbose: bool,

/// File to generate
#[structopt(short = "r", long = "result", parse(from_os_str))]
result_file: PathBuf,

/// Files to process
#[structopt(name = "FILE", parse(from_os_str))]
files: Vec<PathBuf>,
}

fn main() {
println!("{:#?}", Opt::from_args());
}

If you execute the cargo run input1.txt input2.txt -v --result res.xyz command, you should get the following output:

Opt {
verbose: true,
result_file: "res.txt",
files: [
"input1.tx",
"input2.txt"
]
}

As you can see, the filenames input1.txt and input2.txt have been loaded into the files field of the structure. The --result res.xyz argument caused the result_file field to be filled, and the -v argument caused the verbose field to be set to true, instead of the default false.

主站蜘蛛池模板: 大渡口区| 台东县| 清镇市| 德令哈市| 郧西县| 汾西县| 奈曼旗| 万年县| 商都县| 惠安县| 雅江县| 固原市| 读书| 杨浦区| 青浦区| 镇江市| 班戈县| 台北市| 宝坻区| 承德市| 象山县| 若尔盖县| 清镇市| 永仁县| 陆丰市| 始兴县| 鄄城县| 莲花县| 含山县| 西丰县| 抚宁县| 淳化县| 新安县| 锡林浩特市| 闵行区| 托里县| 马关县| 东乡| 福贡县| 利津县| 兴隆县|