PHP Cookbook
PHP Cookbook
This Rust Cookbook is a collection of simple examples that demonstrate good practices to accomplish
common programming tasks, using the crates of the Rust ecosystem.
Read more about Rust Cookbook, including tips for how to read the book, how to use the examples, and
notes on conventions.
Contributing
This project is intended to be easy for new Rust programmers to contribute to, and an easy way to get
involved with the Rust community. It needs and welcomes help. For details see CONTRIBUTING.md.
Algorithms
Recipe Crates Categories
Generate random numbers rand v0.9.0-alpha.1 Science
rand v0.9.0-alpha.1
Generate random numbers with given distribution Science
rand_distr v0.9.0-alpha.1
flate2 v1.0.30
Compress a directory into a tarball Compression
tar v0.4.40
flate2 v1.0.30
Decompress a tarball while removing a prefix from the paths Compression
tar v0.4.40
Concurrency
Recipe Crates Categories
Spawn a short-lived thread crossbeam v0.8.4 Concurrency
threadpool v1.8.1
ring v0.17.8
threadpool v1.8.1
image v0.25.1
Test in parallel if any or all elements of a collection match a rayon v1.10.0 Concurrency
given predicate
Search items using given predicate in parallel rayon v1.10.0 Concurrency
rayon v1.10.0
Sort a vector in parallel Concurrency
rand v0.9.0-alpha.1
rayon v1.10.0
Concurrency
Generate jpg thumbnails in parallel glob v0.3.1
Filesystem
image v0.25.1
Cryptography
Recipe Crates Categories
ring v0.17.8
Calculate the SHA-256 digest of a file Cryptography
data-encoding v2.6.0
Sign and verify a message with an HMAC digest ring v0.17.8 Cryptography
ring v0.17.8
Salt and hash a password with PBKDF2 Cryptography
data-encoding v2.6.0
Data Structures
Recipe Crates Categories
Define and operate on a type represented as a bitfield bitflags v2.5.0 No std
Database
Recipe Crates Categories
Create a SQLite database rusqlite v0.31.0 Database
Perform checked date and time calculations chrono v0.4.38 Date and time
Convert a local time to another timezone chrono v0.4.38 Date and time
Examine the date and time chrono v0.4.38 Date and time
Convert date to UNIX timestamp and vice versa chrono v0.4.38 Date and time
Display formatted date and time chrono v0.4.38 Date and time
Parse string into DateTime struct chrono v0.4.38 Date and time
Development Tools
Debugging
Recipe Crates Categories
log v0.4.21
Log a debug message to the console Debugging
env_logger v0.11.3
log v0.4.21
Log an error message to the console Debugging
env_logger v0.11.3
log v0.4.21
Log to stdout instead of stderr Debugging
env_logger v0.11.3
log v0.4.21
Log to the Unix syslog Debugging
syslog v6.1.1
log v0.4.21
Enable log levels per module Debugging
env_logger v0.11.3
log v0.4.21
Use a custom environment variable to set up logging Debugging
env_logger v0.11.3
log v0.4.21
chrono v0.4.38
log v0.4.21
Log messages to a custom location Debugging
log4rs v1.3.0
Versioning
Recipe Crates Categories
Parse and increment a version string semver v1.0.23 Config
Find the latest version satisfying given range semver v1.0.23 Config
Text processing
Check external command version for compatibility semver v1.0.23
OS
Build Time
Recipe Crates Categories
Compile and link statically to a bundled C library cc v1.0.98 Development tools
Compile and link statically to a bundled C++ library cc v1.0.98 Development tools
csv v1.3.0
Handle invalid CSV data with Serde Encoding
serde v1.0.202
csv v1.3.0
Serialize records to CSV using Serde Encoding
serde v1.0.202
csv v1.3.0
Transform one column of a CSV file Encoding
serde v1.0.202
Read and write integers in little-endian byte order byteorder v1.5.0 Encoding
File System
Recipe Crates Categories
Read lines of strings from a file std 1.29.1 Filesystem
Avoid writing and reading from a same file same_file v1.0.6 Filesystem
File names that have been modified in the last 24 hours std 1.29.1 Filesystem OS
Recursively find all files with given predicate walkdir v2.5.0 Filesystem
Find all files with given pattern ignoring filename case glob v0.3.1 Filesystem
Hardware Support
Recipe Crates Categories
Check number of logical cpu cores num_cpus v1.16.0 Hardware support
Memory Management
Recipe Crates Categories
Caching
Declare lazily evaluated constant lazy_static v1.4.0
Rust patterns
Networking
Recipe Crates Categories
Listen on unused port TCP/IP std 1.29.1 Net
Operating System
Recipe Crates Categories
OS
Run an external command and process stdout regex v1.10.4
Text processing
OS
Run an external command passing it stdin and check for an regex v1.10.4
error code Text processing
Redirect both stdout and stderr of child process to the same std 1.29.1 OS
file
OS
Continuously process child process' outputs std 1.29.1
Text processing
Mathematics
Recipe Crates Categories
Vector Norm ndarray v0.15.6 Science
regex v1.10.4
Verify and extract login from an email address Text processing
lazy_static v1.4.0
regex v1.10.4
Extract a list of unique #Hashtags from a text Text processing
lazy_static v1.4.0
Filter a log file by matching multiple regular expressions regex v1.10.4 Text processing
Replace all occurrences of one text pattern with another regex v1.10.4
Text processing
pattern. lazy_static v1.4.0
Implement the FromStr trait for a custom struct std 1.29.1 Text processing
Web Programming
reqwest v0.12.4
url v2.5.0
reqwest v0.12.4
Extract all unique links from a MediaWiki markup Net
regex v1.10.4
Extract the URL origin (scheme / host / port) url v2.5.0 Net
Remove fragment identifiers and query pairs from a URL url v2.5.0 Net
mime v1.3.0
Parse the MIME type of a HTTP response Net Encoding
reqwest v0.12.4
Clients
Recipe Crates Categories
Make a HTTP GET request reqwest v0.12.4 Net
reqwest v0.12.4
Query the GitHub API Net Encoding
serde v1.0.202
reqwest v0.12.4
Create and delete Gist with GitHub API Net Encoding
serde v1.0.202
reqwest v0.12.4
Consume a paginated RESTful API Net Encoding
serde v1.0.202
reqwest v0.12.4
Download a file to a temporary directory Net Filesystem
tempdir v0.3.7
Make a partial download with HTTP range headers reqwest v0.12.4 Net
Web Authentication
Recipe Crates Categories
Basic Authentication reqwest v0.12.4 Net
About "Cookin' with Rust"
Table of contents
Who this book is for
How to read this book
How to use the recipes
A note about error handling
A note about crate representation
Within the index, each section contains a list of recipes. The recipes are simple statements of a task to
accomplish, like "generate random numbers in a range"; and each recipe is tagged with badges indicating
which crates they use, like rand v0.9.0-alpha.1 , and which categories on crates.io those crates belong to, like
Science .
New Rust programmers should be comfortable reading from the first section to the last, and doing so
should give one a strong overview of the crate ecosystem. Click on the section header in the index, or in the
sidebar to navigate to the page for that section of the book.
If you are simply looking for the solution to a simple task, the cookbook is today more difficult to navigate.
The easiest way to find a specific recipe is to scan the index looking for the crates and categories one is
interested in. From there, click on the name of the recipe to view it. This will improve in the future.
use rand::Rng;
fn main() {
let mut rng = rand::thread_rng();
println!("Random f64: {}", rng.gen::<f64>());
}
To work with it locally we can run the following commands to create a new cargo project, and change to
that directory:
Now, we also need to add the necessary crates to Cargo.toml, as indicated by the crate badges, in this case
just "rand". To do so, we'll use the cargo add command, which is provided by the cargo-edit crate,
which we need to install first:
Now you can replace src/main.rs with the full contents of the example and run it:
cargo run
The crate badges that accompany the examples link to the crates' full documentation on docs.rs, and is
often the next documentation you should read after deciding which crate suites your purpose.
Since these recipes are intended to be reused as-is and encourage best practices, they set up error handling
correctly when there are Result types involved.
error_chain! {
foreign_links {
Utf8(std::str::Utf8Error);
AddrParse(std::net::AddrParseError);
}
}
// Bytes to string.
let s = str::from_utf8(bytes)?;
// String to IP address.
let addr: IpAddr = s.parse()?;
println!("{:?}", addr);
Ok(())
}
This is using the error_chain! macro to define a custom Error and Result type, along with automatic
conversions from two standard library error types. The automatic conversions make the ? operator work.
For the sake of readability error handling boilerplate is hidden by default like below. In order to read full
contents click on the "expand" () button located in the top right corner of the snippet.
For more background on error handling in Rust, read this page of the Rust book and this blog post.
At present the cookbook is focused on the standard library, and on "core", or "foundational", crates—those
crates that make up the most common programming tasks, and that the rest of the ecosystem builds off of.
The cookbook is closely tied to the Rust Libz Blitz, a project to identify, and improve the quality of such
crates, and so it largely defers crate selection to that project. Any crates that have already been evaluated as
part of that process are in scope for the cookbook, as are crates that are pending evaluation.
Algorithms
Recipe Crates Categories
Generate random numbers rand v0.9.0-alpha.1 Science
rand v0.9.0-alpha.1
Generate random numbers with given distribution Science
rand_distr v0.9.0-alpha.1
Generates random numbers with help of random-number generator rand::Rng obtained via
rand::thread_rng . Each thread has an initialized generator. Integers are uniformly distributed over the
range of the type, and floating point numbers are uniformly distributed from 0 up to but not including 1.
use rand::Rng;
fn main() {
let mut rng = rand::thread_rng();
Generates a random value within half-open [0, 10) range (not including 10 ) with Rng::gen_range .
use rand::Rng;
fn main() {
let mut rng = rand::thread_rng();
println!("Integer: {}", rng.gen_range(0..10));
println!("Float: {}", rng.gen_range(0.0..10.0));
}
Uniform can obtain values with uniform distribution. This has the same effect, but may be faster when
repeatedly generating numbers in the same range.
use rand::distributions::{Distribution, Uniform};
fn main() {
let mut rng = rand::thread_rng();
let die = Uniform::from(1..7);
loop {
let throw = die.sample(&mut rng);
println!("Roll the die: {}", throw);
if throw == 6 {
break;
}
}
}
By default, random numbers in the rand crate have uniform distribution. The rand_distr crate provides
other kinds of distributions. To use them, you instantiate a distribution, then sample from that distribution
using Distribution::sample with help of a random-number generator rand::Rng .
The distributions available are documented here. An example using the Normal distribution is shown
below.
Randomly generates a tuple (i32, bool, f64) and variable of user defined type Point . Implements the
Distribution trait on type Point for Standard in order to allow random generation.
use rand::Rng;
use rand::distributions::{Distribution, Standard};
#[derive(Debug)]
struct Point {
x: i32,
y: i32,
}
fn main() {
let mut rng = rand::thread_rng();
let rand_tuple = rng.gen::<(i32, bool, f64)>();
let rand_point: Point = rng.gen();
println!("Random tuple: {:?}", rand_tuple);
println!("Random Point: {:?}", rand_point);
}
Randomly generates a string of given length ASCII characters in the range A-Z, a-z, 0-9 , with
Alphanumeric sample.
fn main() {
let rand_string: String = thread_rng()
.sample_iter(&Alphanumeric)
.take(30)
.map(char::from)
.collect();
println!("{}", rand_string);
}
fn main() {
use rand::Rng;
const CHARSET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ\
abcdefghijklmnopqrstuvwxyz\
0123456789)(*&^%$#@!~";
const PASSWORD_LEN: usize = 30;
let mut rng = rand::thread_rng();
println!("{:?}", password);
}
Sorting Vectors
This example sorts a Vector of integers via vec::sort . Alternative would be to use vec::sort_unstable
which can be faster, but does not preserve the order of equal elements.
fn main() {
let mut vec = vec![1, 5, 10, 2, 15];
vec.sort();
fn main() {
let mut vec = vec![1.1, 1.15, 5.5, 1.123, 2.0];
vec.sort_by(|a, b| a.partial_cmp(b).unwrap());
Sorts a Vector of Person structs with properties name and age by its natural order (By name and age). In
order to make Person sortable you need four traits Eq , PartialEq , Ord and PartialOrd . These traits can
be simply derived. You can also provide a custom comparator function using a vec:sort_by method and
sort only by age.
#[derive(Debug, Eq, Ord, PartialEq, PartialOrd)]
struct Person {
name: String,
age: u32
}
impl Person {
pub fn new(name: String, age: u32) -> Self {
Person {
name,
age
}
}
}
fn main() {
let mut people = vec![
Person::new("Zoe".to_string(), 25),
Person::new("Al".to_string(), 60),
Person::new("John".to_string(), 1),
];
assert_eq!(
people,
vec![
Person::new("Al".to_string(), 60),
Person::new("John".to_string(), 1),
Person::new("Zoe".to_string(), 25),
]);
assert_eq!(
people,
vec![
Person::new("Al".to_string(), 60),
Person::new("Zoe".to_string(), 25),
Person::new("John".to_string(), 1),
]);
}
Command Line
Recipe Crates Categories
Parse command line arguments clap v4.5.4 Command line
This application describes the structure of its command-line interface using clap 's builder style. The
documentation gives two other possible ways to instantiate an application.
In the builder style, with_name is the unique identifier that value_of will use to retrieve the value passed.
The short and long options control the flag the user will be expected to type; short flags look like -f
and long flags look like --file .
fn main() {
let matches = App::new("My Test Program")
.version("0.1.0")
.author("Hackerman Jones <hckrmnjones@hack.gov>")
.about("Teaches argument parsing")
.arg(Arg::with_name("file")
.short("f")
.long("file")
.takes_value(true)
.help("A cool file"))
.arg(Arg::with_name("num")
.short("n")
.long("number")
.takes_value(true)
.help("Five less than your favorite number"))
.get_matches();
Usage information is generated by clap . The usage for the example application looks like this.
My Test Program 0.1.0
Hackerman Jones <hckrmnjones@hack.gov>
Teaches argument parsing
USAGE:
testing [OPTIONS]
FLAGS:
-h, --help Prints help information
-V, --version Prints version information
OPTIONS:
-f, --file <file> A cool file
-n, --number <num> Five less than your favorite number
ANSI Terminal
ansi_term v0.22.1 Command line
This program depicts the use of ansi_term crate and how it is used for controlling colours and formatting,
such as blue bold text or yellow underlined text, on ANSI terminals.
There are two main data structures in ansi_term : ANSIString and Style . A Style holds stylistic
information: colours, whether the text should be bold, or blinking, or whatever. There are also Colour
variants that represent simple foreground colour styles. An ANSIString is a string paired with a Style .
Note: British English uses Colour instead of Color, don't get confused
use ansi_term::Colour;
fn main() {
println!("This is {} in color, {} in color and {} in color",
Colour::Red.paint("red"),
Colour::Blue.paint("blue"),
Colour::Green.paint("green"));
}
For anything more complex than plain foreground colour changes, the code needs to construct Style
struct. Style::new() creates the struct, and properties chained.
use ansi_term::Style;
fn main() {
println!("{} and this is not",
Style::new().bold().paint("This is Bold"));
}
Colour implements many similar functions as Style and can chain methods.
use ansi_term::Colour;
use ansi_term::Style;
fn main(){
println!("{}, {} and {}",
Colour::Yellow.paint("This is colored"),
Style::new().bold().paint("this is bold"),
Colour::Yellow.bold().paint("this is bold and colored"));
}
Compression
Recipe Crates Categories
flate2 v1.0.30
Decompress a tarball Compression
tar v0.4.40
flate2 v1.0.30
Compress a directory into a tarball Compression
tar v0.4.40
flate2 v1.0.30
Decompress a tarball while removing a prefix from the paths Compression
tar v0.4.40
Working with Tarballs
Decompress a tarball
flate2 v1.0.30 tar v0.4.40 Compression
Decompress ( GzDecoder ) and extract ( Archive::unpack ) all files from a compressed tarball named
archive.tar.gz located in the current working directory to the same location.
use std::fs::File;
use flate2::read::GzDecoder;
use tar::Archive;
Ok(())
}
use std::fs::File;
use flate2::Compression;
use flate2::write::GzEncoder;
Iterate over the Archive::entries . Use Path::strip_prefix to remove the specified path prefix
( bundle/logs ). Finally, extract the tar::Entry via Entry::unpack .
use std::fs::File;
use std::path::PathBuf;
use flate2::read::GzDecoder;
use tar::Archive;
Ok(())
}
Concurrency
Recipe Crates Categories
Spawn a short-lived thread crossbeam v0.8.4 Concurrency
threadpool v1.8.1
ring v0.17.8
threadpool v1.8.1
image v0.25.1
Test in parallel if any or all elements of a collection match a rayon v1.10.0 Concurrency
given predicate
Search items using given predicate in parallel rayon v1.10.0 Concurrency
rayon v1.10.0
Sort a vector in parallel Concurrency
rand v0.9.0-alpha.1
rayon v1.10.0
Concurrency
Generate jpg thumbnails in parallel glob v0.3.1
Filesystem
image v0.25.1
Threads
The example uses the crossbeam crate, which provides data structures and functions for concurrent and
parallel programming. Scope::spawn spawns a new scoped thread that is guaranteed to terminate before
returning from the closure that passed into crossbeam::scope function, meaning that you can reference
data from the calling function.
This example splits the array in half and performs the work in separate threads.
fn main() {
let arr = &[1, 25, -4, 10];
let max = find_max(arr);
assert_eq!(max, Some(25));
}
crossbeam::scope(|s| {
let thread_l = s.spawn(|_| find_max(left));
let thread_r = s.spawn(|_| find_max(right));
Some(max_l.max(max_r))
}).unwrap()
}
This example uses the crossbeam and crossbeam-channel crates to create a parallel pipeline, similar to
that described in the ZeroMQ guide There is a data source and a data sink, with data being processed by
two worker threads in parallel on its way from the source to the sink.
We use bounded channels with a capacity of one using crossbeam_channel::bounded . The producer must
be on its own thread because it produces messages faster than the workers can process them (since they
sleep for half a second) - this means the producer blocks on the call to
[crossbeam_channel::Sender::send ] for half a second until one of the workers processes the data in the
channel. Also note that the data in the channel is consumed by whichever worker calls receive first, so each
message is delivered to a single worker rather than both workers.
Reading from the channels via the iterator crossbeam_channel::Receiver::iter method will block, either
waiting for new messages or until the channel is closed. Because the channels were created within the
crossbeam::scope , we must manually close them via drop to prevent the entire program from blocking
on the worker for-loops. You can think of the calls to drop as signaling that no more messages will be
sent.
extern crate crossbeam;
extern crate crossbeam_channel;
use std::thread;
use std::time::Duration;
use crossbeam_channel::bounded;
fn main() {
let (snd1, rcv1) = bounded(1);
let (snd2, rcv2) = bounded(1);
let n_msgs = 4;
let n_workers = 2;
crossbeam::scope(|s| {
// Producer thread
s.spawn(|_| {
for i in 0..n_msgs {
snd1.send(i).unwrap();
println!("Source sent {}", i);
}
// Close the channel - this is necessary to exit
// the for-loop in the worker
drop(snd1);
});
// Sink
for msg in rcv2.iter() {
println!("Sink received {}", msg);
}
}).unwrap();
}
Pass data between two threads
crossbeam v0.8.4 Concurrency
This example demonstrates the use of crossbeam-channel in a single producer, single consumer (SPSC)
setting. We build off the ex-crossbeam-spawn example by using crossbeam::scope and Scope::spawn
to manage the producer thread. Data is exchanged between the two threads using a
crossbeam_channel::unbounded channel, meaning there is no limit to the number of storeable messages.
The producer thread sleeps for half a second in between messages.
fn main() {
let (snd, rcv) = unbounded();
let n_msgs = 5;
crossbeam::scope(|s| {
s.spawn(|_| {
for i in 0..n_msgs {
snd.send(i).unwrap();
thread::sleep(time::Duration::from_millis(100));
}
});
}).unwrap();
for _ in 0..n_msgs {
let msg = rcv.recv().unwrap();
println!("Received {}", msg);
}
}
Declare global state using lazy_static. lazy_static creates a globally available static ref which requires a
Mutex to allow mutation (also see RwLock ). The Mutex wrap ensures the state cannot be simultaneously
accessed by multiple threads, preventing race conditions. A MutexGuard must be acquired to read or
mutate the value stored in a Mutex .
use lazy_static::lazy_static;
use std::sync::Mutex;
lazy_static! {
static ref FRUIT: Mutex<Vec<String>> = Mutex::new(Vec::new());
}
This example calculates the SHA256 for every file with iso extension in the current directory. A threadpool
generates threads equal to the number of cores present in the system found with num_cpus::get .
Walkdir::new iterates the current directory and calls execute to perform the operations of reading and
computing SHA256 hash.
use walkdir::WalkDir;
use std::fs::File;
use std::io::{BufReader, Read, Error};
use std::path::Path;
use threadpool::ThreadPool;
use std::sync::mpsc::channel;
use ring::digest::{Context, Digest, SHA256};
loop {
let count = buf_reader.read(&mut buffer)?;
if count == 0 {
break;
}
context.update(&buffer[..count]);
}
Ok((context.finish(), filepath))
}
drop(tx);
for t in rx.iter() {
let (sha, path) = t?;
println!("{:?} {:?}", sha, path);
}
Ok(())
}
Allocate memory for output image of given width and height with ImageBuffer::new .
Rgb::from_channels calculates RGB pixel values. Create ThreadPool with thread count equal to number
of cores with num_cpus::get . ThreadPool::execute receives each pixel as a separate job.
mpsc::channel receives the jobs and Receiver::recv retrieves them. ImageBuffer::put_pixel uses the
data to set the pixel color. ImageBuffer::save writes the image to output.png .
for y in 0..height {
let tx = tx.clone();
pool.execute(move || for x in 0..width {
let i = julia(c, x, y, width, height, iterations);
let pixel = wavelength_to_rgb(380 + i * 400 / iterations);
tx.send((x, y, pixel)).expect("Could not send data!");
});
}
The example uses the rayon crate, which is a data parallelism library for Rust. rayon provides the
par_iter_mut method for any parallel iterable data type. This is an iterator-like chain that potentially
executes in parallel.
use rayon::prelude::*;
fn main() {
let mut arr = [0, 7, 9, 11];
arr.par_iter_mut().for_each(|p| *p -= 1);
println!("{:?}", arr);
}
This example demonstrates using the rayon::any and rayon::all methods, which are parallelized
counterparts to std::any and std::all . rayon::any checks in parallel whether any element of the
iterator matches the predicate, and returns as soon as one is found. rayon::all checks in parallel whether
all elements of the iterator match the predicate, and returns as soon as a non-matching element is found.
use rayon::prelude::*;
fn main() {
let mut vec = vec![2, 4, 6, 8];
vec.push(9);
This example uses rayon::find_any and par_iter to search a vector in parallel for an element satisfying
the predicate in the given closure.
If there are multiple elements satisfying the predicate defined in the closure argument of rayon::find_any ,
rayon returns the first one found, not necessarily the first one.
Also note that the argument to the closure is a reference to a reference ( &&x ). See the discussion on
std::find for additional details.
use rayon::prelude::*;
fn main() {
let v = vec![6, 2, 1, 9, 3, 8, 11];
assert_eq!(f1, Some(&9));
assert_eq!(f2, Some(&8));
assert!(f3 > Some(&8));
}
fn main() {
let mut vec = vec![String::new(); 100_000];
vec.par_iter_mut().for_each(|p| {
let mut rng = thread_rng();
*p = (0..5).map(|_| rng.sample(&Alphanumeric)).collect()
});
vec.par_sort_unstable();
}
Map-reduce in parallel
rayon v1.10.0 Concurrency
This example uses rayon::filter , rayon::map , and rayon::reduce to calculate the average age of
Person objects whose age is over 30.
rayon::filter returns elements from a collection that satisfy the given predicate. rayon::map performs
an operation on every element, creating a new iteration, and rayon::reduce performs an operation given
the previous reduction and the current element. Also shows use of rayon::sum , which has the same result
as the reduce operation in this example.
use rayon::prelude::*;
struct Person {
age: u32,
}
fn main() {
let v: Vec<Person> = vec![
Person { age: 23 },
Person { age: 19 },
Person { age: 42 },
Person { age: 17 },
Person { age: 17 },
Person { age: 31 },
Person { age: 30 },
];
This example generates thumbnails for all .jpg files in the current directory then saves them in a new folder
called thumbnails .
glob::glob_with finds jpeg files in current directory. rayon resizes images in parallel using par_iter
calling DynamicImage::resize .
use std::path::Path;
use std::fs::create_dir_all;
if files.len() == 0 {
error_chain::bail!("No .jpg files found in current directory");
}
Sign and verify a message with an HMAC digest ring v0.17.8 Cryptography
ring v0.17.8
Salt and hash a password with PBKDF2 Cryptography
data-encoding v2.6.0
Hashing
Writes some data to a file, then calculates the SHA-256 digest::Digest of the file's contents using
digest::Context .
use data_encoding::HEXUPPER;
use ring::digest::{Context, Digest, SHA256};
use std::fs::File;
use std::io::{BufReader, Read, Write};
loop {
let count = reader.read(&mut buffer)?;
if count == 0 {
break;
}
context.update(&buffer[..count]);
}
Ok(context.finish())
}
Ok(())
}
Uses ring::hmac to creates a hmac::Signature of a string then verifies the signature is correct.
use ring::{hmac, rand};
use ring::rand::SecureRandom;
use ring::error::Unspecified;
Ok(())
}
Encryption
Uses ring::pbkdf2 to hash a salted password using the PBKDF2 key derivation function
pbkdf2::derive . Verifies the hash is correct with pbkdf2::verify . The salt is generated using
SecureRandom::fill , which fills the salt byte array with securely generated random numbers.
use data_encoding::HEXUPPER;
use ring::error::Unspecified;
use ring::rand::SecureRandom;
use ring::{digest, pbkdf2, rand};
use std::num::NonZeroU32;
assert!(should_succeed.is_ok());
assert!(!should_fail.is_ok());
Ok(())
}
Data Structures
Recipe Crates Categories
Define and operate on a type represented as a bitfield bitflags v2.5.0 No std
Custom
Creates type safe bitfield type MyFlags with help of bitflags! macro and implements elementary clear
operation as well as Display trait for it. Subsequently, shows basic bitwise operations and formatting.
use bitflags::bitflags;
use std::fmt;
bitflags! {
struct MyFlags: u32 {
const FLAG_A = 0b00000001;
const FLAG_B = 0b00000010;
const FLAG_C = 0b00000100;
const FLAG_ABC = Self::FLAG_A.bits
| Self::FLAG_B.bits
| Self::FLAG_C.bits;
}
}
impl MyFlags {
pub fn clear(&mut self) -> &mut MyFlags {
self.bits = 0;
self
}
}
fn main() {
let e1 = MyFlags::FLAG_A | MyFlags::FLAG_C;
let e2 = MyFlags::FLAG_B | MyFlags::FLAG_C;
assert_eq!((e1 | e2), MyFlags::FLAG_ABC);
assert_eq!((e1 & e2), MyFlags::FLAG_C);
assert_eq!((e1 - e2), MyFlags::FLAG_A);
assert_eq!(!e2, MyFlags::FLAG_A);
Use the rusqlite crate to open SQLite databases. See crate for compiling on Windows.
conn.execute(
"create table if not exists cat_colors (
id integer primary key,
name text not null unique
)",
NO_PARAMS,
)?;
conn.execute(
"create table if not exists cats (
id integer primary key,
name text not null,
color_id integer not null references cat_colors(id)
)",
NO_PARAMS,
)?;
Ok(())
}
Connection::open will open the database cats created in the earlier recipe. This recipe inserts data into
cat_colors and cats tables using the execute method of Connection . First, the data is inserted into
the cat_colors table. After a record for a color is inserted, last_insert_rowid method of Connection is
used to get id of the last color inserted. This id is used while inserting data into the cats table. Then,
the select query is prepared using the prepare method which gives a statement struct. Then, query is
executed using query_map method of statement .
use rusqlite::NO_PARAMS;
use rusqlite::{Connection, Result};
use std::collections::HashMap;
#[derive(Debug)]
struct Cat {
name: String,
color: String,
}
Ok(())
}
Using transactions
rusqlite v0.31.0 Database
Connection::open will open the cats.db database from the top recipe.
Begin a transaction with Connection::transaction . Transactions will roll back unless committed
explicitly with Transaction::commit .
In the following example, colors add to a table having a unique constraint on the color name. When an
attempt to insert a duplicate color is made, the transaction rolls back.
successful_tx(&mut conn)?;
Ok(())
}
tx.commit()
}
tx.commit()
}
Working with Postgres
Client::connect helps in connecting to an existing database. The recipe uses a URL string format with
Client::connect . It assumes an existing database named library , the username is postgres and the
password is postgres .
client.batch_execute("
CREATE TABLE IF NOT EXISTS author (
id SERIAL PRIMARY KEY,
name VARCHAR NOT NULL,
country VARCHAR NOT NULL
)
")?;
client.batch_execute("
CREATE TABLE IF NOT EXISTS book (
id SERIAL PRIMARY KEY,
title VARCHAR NOT NULL,
author_id INTEGER NOT NULL REFERENCES author
)
")?;
Ok(())
The recipe inserts data into the author table using execute method of Client . Then, displays the data
from the author table using query method of Client .
use postgres::{Client, NoTls, Error};
use std::collections::HashMap;
struct Author {
_id: i32,
name: String,
country: String
}
client.execute(
"INSERT INTO author (name, country) VALUES ($1, $2)",
&[&author.name, &author.country],
)?;
}
Ok(())
Aggregate data
postgres v0.19.7 Database
This recipe lists the nationalities of the first 7999 artists in the database of the Museum of Modern Art in
descending order.
use postgres::{Client, Error, NoTls};
struct Nation {
nationality: String,
count: i64,
}
}
}
Ok(())
}
Date and Time
Recipe Crates Categories
Measure elapsed time std 1.29.1 Time
Perform checked date and time calculations chrono v0.4.38 Date and time
Convert a local time to another timezone chrono v0.4.38 Date and time
Examine the date and time chrono v0.4.38 Date and time
Convert date to UNIX timestamp and vice versa chrono v0.4.38 Date and time
Display formatted date and time chrono v0.4.38 Date and time
Parse string into DateTime struct chrono v0.4.38 Date and time
Duration and Calculation
Calling time::Instant::elapsed returns a time::Duration that we print at the end of the example. This
method will not mutate or reset the time::Instant object.
fn main() {
let start = Instant::now();
expensive_function();
let duration = start.elapsed();
Calculates and displays the date and time two weeks from now using DateTime::checked_add_signed
and the date of the day before that using DateTime::checked_sub_signed . The methods return None if the
date and time cannot be calculated.
Escape sequences that are available for the DateTime::format can be found at
chrono::format::strftime .
use chrono::{DateTime, Duration, Utc};
fn main() {
let now = Utc::now();
println!("{}", now);
match almost_three_weeks_from_now {
Some(x) => println!("{}", x),
None => eprintln!("Almost three weeks from now overflows!"),
}
match now.checked_add_signed(Duration::max_value()) {
Some(x) => println!("{}", x),
None => eprintln!("We can't use chrono to tell the time for the Solar System to
complete more than one full orbit around the galactic center."),
}
}
Gets the local time and displays it using offset::Local::now and then converts it to the UTC standard
using the DateTime::from_utc struct method. A time is then converted using the offset::FixedOffset
struct and the UTC time is then converted to UTC+8 and UTC-2.
fn main() {
let local_time = Local::now();
let utc_time = DateTime::<Utc>::from_utc(local_time.naive_utc(), Utc);
let china_timezone = FixedOffset::east(8 * 3600);
let rio_timezone = FixedOffset::west(2 * 3600);
println!("Local time now is {}", local_time);
println!("UTC time now is {}", utc_time);
println!(
"Time in Hong Kong now is {}",
utc_time.with_timezone(&china_timezone)
);
println!("Time in Rio de Janeiro now is {}", utc_time.with_timezone(&rio_timezone));
}
Parsing and Displaying
Gets the current UTC DateTime and its hour/minute/second via Timelike and its year/month/day/weekday
via Datelike .
fn main() {
let now = Utc::now();
fn main() {
let date_time: NaiveDateTime = NaiveDate::from_ymd(2017, 11, 12).and_hms(17, 33, 44);
println!(
"Number of seconds between 1970-01-01 00:00:00 and {} is {}.",
date_time, date_time.timestamp());
Gets and displays the current time in UTC using Utc::now . Formats the current time in the well-known
formats RFC 2822 using DateTime::to_rfc2822 and RFC 3339 using DateTime::to_rfc3339 , and in a
custom format using DateTime::format .
fn main() {
let now: DateTime<Utc> = Utc::now();
Parses a DateTime struct from strings representing the well-known formats RFC 2822, RFC 3339, and a
custom format, using DateTime::parse_from_rfc2822 , DateTime::parse_from_rfc3339 , and
DateTime::parse_from_str respectively.
Escape sequences that are available for the DateTime::parse_from_str can be found at
chrono::format::strftime . Note that the DateTime::parse_from_str requires that such a DateTime
struct must be creatable that it uniquely identifies a date and a time. For parsing dates and times without
timezones use NaiveDate , NaiveTime , and NaiveDateTime .
use chrono::{DateTime, NaiveDate, NaiveDateTime, NaiveTime};
use chrono::format::ParseError;
Ok(())
}
Development Tools
Debugging
Recipe Crates Categories
log v0.4.21
Log a debug message to the console Debugging
env_logger v0.11.3
log v0.4.21
Log an error message to the console Debugging
env_logger v0.11.3
log v0.4.21
Log to stdout instead of stderr Debugging
env_logger v0.11.3
log v0.4.21
Log to the Unix syslog Debugging
syslog v6.1.1
log v0.4.21
Enable log levels per module Debugging
env_logger v0.11.3
log v0.4.21
Use a custom environment variable to set up logging Debugging
env_logger v0.11.3
log v0.4.21
chrono v0.4.38
log v0.4.21
Log messages to a custom location Debugging
log4rs v1.3.0
Versioning
Recipe Crates Categories
Parse and increment a version string semver v1.0.23 Config
Find the latest version satisfying given range semver v1.0.23 Config
Text processing
Check external command version for compatibility semver v1.0.23
OS
Build Time
Recipe Crates Categories
Compile and link statically to a bundled C library cc v1.0.98 Development tools
Compile and link statically to a bundled C++ library cc v1.0.98 Development tools
Debugging
Recipe Crates Categories
log v0.4.21
Log a debug message to the console Debugging
env_logger v0.11.3
log v0.4.21
Log an error message to the console Debugging
env_logger v0.11.3
log v0.4.21
Log to stdout instead of stderr Debugging
env_logger v0.11.3
log v0.4.21
Log to the Unix syslog Debugging
syslog v6.1.1
log v0.4.21
Enable log levels per module Debugging
env_logger v0.11.3
log v0.4.21
Use a custom environment variable to set up logging Debugging
env_logger v0.11.3
log v0.4.21
chrono v0.4.38
log v0.4.21
Log messages to a custom location Debugging
log4rs v1.3.0
Log Messages
The log crate provides logging utilities. The env_logger crate configures logging via an environment
variable. The log::debug! macro works like other std::fmt formatted strings.
fn execute_query(query: &str) {
log::debug!("Executing query: {}", query);
}
fn main() {
env_logger::init();
No output prints when running this code. By default, the log level is error , and any lower levels are
dropped.
Cargo prints debugging information then the following line at the very end of the output:
Proper error handling considers exceptions exceptional. Here, an error logs to stderr with log 's
convenience macro log::error! .
fn execute_query(_query: &str) -> Result<(), &'static str> {
Err("I'm afraid I can't do that")
}
fn main() {
env_logger::init();
Creates a custom logger configuration using the Builder::target to set the target of the log output to
Target::Stdout .
fn main() {
Builder::new()
.target(Target::Stdout)
.init();
Implements a custom logger ConsoleLogger which prints to stdout. In order to use the logging macros,
ConsoleLogger implements the log::Log trait and log::set_logger installs it.
use log::{Record, Level, Metadata, LevelFilter, SetLoggerError};
struct ConsoleLogger;
fn flush(&self) {}
}
log::info!("hello log");
log::warn!("warning");
log::error!("oops");
Ok(())
}
Logs messages to UNIX syslog. Initializes logger backend with syslog::init . syslog::Facility records
the program submitting the log entry's classification, log::LevelFilter denotes allowed log verbosity
and Option<&str> holds optional application name.
Creates two modules foo and nested foo::bar with logging directives controlled separately with
RUST_LOG environmental variable.
mod foo {
mod bar {
pub fn run() {
log::warn!("[bar] warn");
log::info!("[bar] info");
log::debug!("[bar] debug");
}
}
pub fn run() {
log::warn!("[foo] warn");
log::info!("[foo] info");
log::debug!("[foo] debug");
bar::run();
}
}
fn main() {
env_logger::init();
log::warn!("[root] warn");
log::info!("[root] info");
log::debug!("[root] debug");
foo::run();
}
RUST_LOG environment variable controls env_logger output. Module declarations take comma separated
entries formatted like path::to::module=log_level . Run the test application as follows:
RUST_LOG="warn,test::foo=info,test::foo::bar=debug" ./test
Sets the default log::Level to warn , module foo and module foo::bar to info and debug .
Builder::parse parses MY_APP_LOG environment variable contents in the form of RUST_LOG syntax.
Then, Builder::init initializes the logger. All these steps are normally done internally by
env_logger::init .
use std::env;
use env_logger::Builder;
fn main() {
Builder::new()
.parse(&env::var("MY_APP_LOG").unwrap_or_default())
.init();
log::info!("informational message");
log::warn!("warning message");
log::error!("this is an error {}", "message");
}
Creates a custom logger configuration with Builder . Each log entry calls Local::now to get the current
DateTime in local timezone and uses DateTime::format with strftime::specifiers to format a
timestamp used in the final log.
The example calls Builder::format to set a closure which formats each message text with timestamp,
Record::level and body ( Record::args ).
use std::io::Write;
use chrono::Local;
use env_logger::Builder;
use log::LevelFilter;
fn main() {
Builder::new()
.format(|buf, record| {
writeln!(buf,
"{} [{}] - {}",
Local::now().format("%Y-%m-%dT%H:%M:%S"),
record.level(),
record.args()
)
})
.filter(None, LevelFilter::Info)
.init();
log::warn!("warn");
log::info!("info");
log::debug!("debug");
}
log4rs configures log output to a custom location. log4rs can use either an external YAML file or a builder
configuration.
log4rs::init_config(config)?;
log::info!("Hello, world!");
Ok(())
}
Versioning
Constructs a semver::Version from a string literal using Version::parse , then increments it by patch,
minor, and major version number one by one.
Note that in accordance with the Semantic Versioning Specification, incrementing the minor version number
resets the patch version number to 0 and incrementing the major version number resets both the minor and
patch version numbers to 0.
assert_eq!(
parsed_version,
Version {
major: 0,
minor: 2,
patch: 6,
pre: vec![],
build: vec![],
}
);
parsed_version.increment_patch();
assert_eq!(parsed_version.to_string(), "0.2.7");
println!("New patch release: v{}", parsed_version);
parsed_version.increment_minor();
assert_eq!(parsed_version.to_string(), "0.3.0");
println!("New minor release: v{}", parsed_version);
parsed_version.increment_major();
assert_eq!(parsed_version.to_string(), "1.0.0");
println!("New major release: v{}", parsed_version);
Ok(())
}
Constructs a semver::Version from a complex version string using Version::parse . The string contains
pre-release and build metadata as defined in the Semantic Versioning Specification.
Note that, in accordance with the Specification, build metadata is parsed but not considered when
comparing versions. In other words, two versions may be equal even if their build strings differ.
assert_eq!(
parsed_version,
Version {
major: 1,
minor: 0,
patch: 49,
pre: vec![Identifier::Numeric(125)],
build: vec![],
}
);
assert_eq!(
parsed_version.build,
vec![Identifier::AlphaNumeric(String::from("g72ee7853"))]
);
Ok(())
}
Given two versions, is_prerelease asserts that one is pre-release and the other is not.
assert!(version_1.is_prerelease());
assert!(!version_2.is_prerelease());
Ok(())
}
Ok(
iterable
.into_iter()
.filter_map(|s| Version::parse(s).ok())
.filter(|s| vreq.matches(s))
.max(),
)
}
assert_eq!(
find_max_matching_version(
">1.2.3-alpha.3",
vec![
"1.2.3-alpha.3",
"1.2.3-alpha.4",
"1.2.3-alpha.10",
"1.2.3-beta.4",
"3.4.5-alpha.9",
]
)?,
Some(Version::parse("1.2.3-beta.4")?)
);
Ok(())
}
Runs git --version using Command , then parses the version number into a semver::Version using
Version::parse . VersionReq::matches compares semver::VersionReq to the parsed version. The
command output resembles "git version x.y.z".
use std::process::Command;
use semver::{Version, VersionReq};
if !output.status.success() {
error_chain::bail!("Command executed with failing error code");
}
if !version_test.matches(&parsed_version) {
error_chain::bail!("Command version lower than minimum supported version (found {},
need {})",
parsed_version, version_constraint);
}
Ok(())
}
Build Time Tooling
This section covers "build-time" tooling, or code that is run prior to compiling a crate's source code.
Conventionally, build-time code lives in a build.rs file and is commonly referred to as a "build script".
Common use cases include rust code generation and compilation of bundled C/C++/asm code. See
crates.io's documentation on the matter for more information.
To accommodate scenarios where additional C, C++, or assembly is required in a project, the cc crate offers
a simple api for compiling bundled C/C++/asm code into static libraries (.a) that can be statically linked to
by rustc.
The following example has some bundled C code (src/hello.c) that will be used from rust. Before
compiling rust source code, the "build" file (build.rs) specified in Cargo.toml runs. Using the cc crate, a
static library file will be produced (in this case, libhello.a, see compile docs) which can then be used from
rust by declaring the external function signatures in an extern block.
Since the bundled C is very simple, only a single source file needs to be passed to cc::Build . For more
complex build requirements, cc::Build offers a full suite of builder methods for specifying include
paths and extra compiler flag s.
Cargo.toml
[package]
...
build = "build.rs"
[build-dependencies]
cc = "1"
[dependencies]
error-chain = "0.11"
build.rs
fn main() {
cc::Build::new()
.file("src/hello.c")
.compile("hello"); // outputs `libhello.a`
}
src/hello.c
#include <stdio.h>
void hello() {
printf("Hello from C!\n");
}
src/main.rs
use error_chain::error_chain;
use std::ffi::CString;
use std::os::raw::c_char;
error_chain! {
foreign_links {
NulError(::std::ffi::NulError);
Io(::std::io::Error);
}
}
fn prompt(s: &str) -> Result<String> {
use std::io::Write;
print!("{}", s);
std::io::stdout().flush()?;
let mut input = String::new();
std::io::stdin().read_line(&mut input)?;
Ok(input.trim().to_string())
}
extern {
fn hello();
fn greet(name: *const c_char);
}
Linking a bundled C++ library is very similar to linking a bundled C library. The two core differences when
compiling and statically linking a bundled C++ library are specifying a C++ compiler via the builder method
cpp(true) and preventing name mangling by the C++ compiler by adding the extern "C" section at the
top of our C++ source file.
Cargo.toml
[package]
...
build = "build.rs"
[build-dependencies]
cc = "1"
build.rs
fn main() {
cc::Build::new()
.cpp(true)
.file("src/foo.cpp")
.compile("foo");
}
src/foo.cpp
extern "C" {
int multiply(int x, int y);
}
src/main.rs
extern {
fn multiply(x : i32, y : i32) -> i32;
}
fn main(){
unsafe {
println!("{}", multiply(5,7));
}
}
Cargo.toml
[package]
...
version = "1.0.2"
build = "build.rs"
[build-dependencies]
cc = "1"
build.rs
fn main() {
cc::Build::new()
.define("APP_NAME", "\"foo\"")
.define("VERSION", format!("\"{}\"", env!("CARGO_PKG_VERSION")).as_str())
.define("WELCOME", None)
.file("src/foo.c")
.compile("foo");
}
src/foo.c
#include <stdio.h>
void print_app_info() {
#ifdef WELCOME
printf("Welcome to ");
#endif
printf("%s - version %s\n", APP_NAME, VERSION);
}
src/main.rs
extern {
fn print_app_info();
}
fn main(){
unsafe {
print_app_info();
}
}
Encoding
Recipe Crates Categories
Percent-encode a string percent-encoding v2.3.1 Encoding
csv v1.3.0
Handle invalid CSV data with Serde Encoding
serde v1.0.202
csv v1.3.0
Serialize records to CSV using Serde Encoding
serde v1.0.202
csv v1.3.0
Transform one column of a CSV file Encoding
serde v1.0.202
Read and write integers in little-endian byte order byteorder v1.5.0 Encoding
Character Sets
Percent-encode a string
percent-encoding v2.3.1 Encoding
Encode an input string with percent-encoding using the utf8_percent_encode function from the percent-
encoding crate. Then decode using the percent_decode function.
/// https://url.spec.whatwg.org/#fragment-percent-encode-set
const FRAGMENT: &AsciiSet = &CONTROLS.add(b' ').add(b'"').add(b'<').add(b'>').add(b'`');
Ok(())
}
The encode set defines which bytes (in addition to non-ASCII and controls) need to be percent-encoded.
The choice of this set depends on context. For example, url encodes ? in a URL path but not in a query
string.
The return value of encoding is an iterator of &str slices which collect into a String .
fn main() {
let urlencoded: String = byte_serialize("What is ❤?".as_bytes()).collect();
assert_eq!(urlencoded, "What+is+%E2%9D%A4%3F");
println!("urlencoded:'{}'", urlencoded);
The data_encoding crate provides a HEXUPPER::encode method which takes a &[u8] and returns a
String containing the hexadecimal representation of the data.
Similarly, a HEXUPPER::decode method is provided which takes a &[u8] and returns a Vec<u8> if the
input data is successfully decoded.
The example below coverts &[u8] data to hexadecimal equivalent. Compares this value to the expected
value.
Ok(())
}
Encodes byte slice into base64 String using encode and decodes it with decode .
use std::str;
use base64::{encode, decode};
Ok(())
}
CSV processing
Reads standard CSV records into csv::StringRecord — a weakly typed data representation which expects
valid UTF-8 rows. Alternatively, csv::ByteRecord makes no assumptions about UTF-8.
use csv::Error;
Ok(())
}
Serde deserializes data into strongly type structures. See the csv::Reader::deserialize method.
use serde::Deserialize;
#[derive(Deserialize)]
struct Record {
year: u16,
make: String,
model: String,
description: String,
}
Ok(())
}
use csv::ReaderBuilder;
Ok(())
}
Returns only the rows from data with a field that matches query .
use std::io;
wtr.write_record(rdr.headers()?)?;
wtr.flush()?;
Ok(())
}
Disclaimer: this example has been adapted from the csv crate tutorial.
CSV files often contain invalid data. For these cases, the csv crate provides a custom deserializer,
csv::invalid_option , which automatically converts invalid data to None values.
use csv::Error;
use serde::Deserialize;
#[derive(Debug, Deserialize)]
struct Record {
name: String,
place: String,
#[serde(deserialize_with = "csv::invalid_option")]
id: Option<u64>,
}
Ok(())
}
This example shows how to serialize a Rust tuple. csv::writer supports automatic serialization from
Rust types into CSV records. write_record writes a simple record containing string data only. Data with
more complex values such as numbers, floats, and options use serialize . Since CSV writer uses internal
buffer, always explicitly flush when done.
use std::io;
wtr.flush()?;
Ok(())
}
Serialize records to CSV using Serde
csv v1.3.0 serde v1.0.202 Encoding
The following example shows how to serialize custom structs as CSV records using the serde crate.
use serde::Serialize;
use std::io;
#[derive(Serialize)]
struct Record<'a> {
name: &'a str,
place: &'a str,
id: u64,
}
wtr.serialize(rec1)?;
wtr.serialize(rec2)?;
wtr.serialize(rec3)?;
wtr.flush()?;
Ok(())
}
Transform a CSV file containing a color name and a hex color into one with a color name and an rgb color.
Utilizes the csv crate to read and write the csv file, and serde to deserialize and serialize the rows to and
from bytes.
#[derive(Debug)]
struct HexColor {
red: u8,
green: u8,
blue: u8,
}
#[derive(Debug, Deserialize)]
struct Row {
color_name: String,
color: HexColor,
}
Unstructured JSON can be parsed into a universal serde_json::Value type that is able to represent any
valid JSON data.
The example below shows a &str of JSON being parsed. The expected value is declared using the json!
macro.
use serde_json::json;
use serde_json::{Value, Error};
assert_eq!(parsed, expected);
Ok(())
}
Parse some TOML into a universal toml::Value that is able to represent any valid TOML data.
use toml::{Value, de::Error};
[dependencies]
serde = "1.0"
"#;
assert_eq!(package_info["dependencies"]["serde"].as_str(), Some("1.0"));
assert_eq!(package_info["package"]["name"].as_str(),
Some("your_package"));
Ok(())
}
use toml::de::Error;
use std::collections::HashMap;
#[derive(Deserialize)]
struct Config {
package: Package,
dependencies: HashMap<String, String>,
}
#[derive(Deserialize)]
struct Package {
name: String,
version: String,
authors: Vec<String>,
}
[dependencies]
serde = "1.0"
"#;
assert_eq!(package_info.package.name, "your_package");
assert_eq!(package_info.package.version, "0.1.0");
assert_eq!(package_info.package.authors, vec!["You! <you@example.org>"]);
assert_eq!(package_info.dependencies["serde"], "1.0");
Ok(())
}
byteorder can reverse the significant bytes of structured data. This may be necessary when receiving
information over the network, such that bytes received are from another system.
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
use std::io::Error;
Avoid discarding errors during error conversions error-chain v0.12.4 Rust patterns
Handles error that occur when trying to open a file that does not exist. It is achieved by using error-chain, a
library that takes care of a lot of boilerplate code needed in order to handle errors in Rust.
The below recipe will tell how long the system has been running by opening the Unix file /proc/uptime
and parse the content to get the first number. Returns uptime unless there is an error.
Other recipes in this book will hide the error-chain boilerplate, and can be seen by expanding the code with
the ⤢ button.
use error_chain::error_chain;
use std::fs::File;
use std::io::Read;
error_chain!{
foreign_links {
Io(std::io::Error);
ParseInt(::std::num::ParseIntError);
}
}
Ok(uptime
.split('.')
.next()
.ok_or("Cannot parse uptime data")?
.parse()?)
}
fn main() {
match read_uptime() {
Ok(uptime) => println!("uptime: {} seconds", uptime),
Err(err) => eprintln!("error: {}", err),
};
}
Avoid discarding errors during error conversions
error-chain v0.12.4 Rust patterns
The error-chain crate makes matching on different error types returned by a function possible and relatively
compact. ErrorKind determines the error type.
Uses reqwest::blocking to query a random integer generator web service. Converts the string response into
an integer. The Rust standard library, reqwest, and the web service can all generate errors. Well defined
Rust errors use foreign_links . An additional ErrorKind variant for the web service error uses errors
block of the error_chain! macro.
use error_chain::error_chain;
error_chain! {
foreign_links {
Io(std::io::Error);
Reqwest(reqwest::Error);
ParseIntError(std::num::ParseIntError);
}
errors { RandomResponseError(t: String) }
}
fn main() {
if let Err(error) = run() {
match *error.kind() {
ErrorKind::Io(_) => println!("Standard IO error: {:?}", error),
ErrorKind::Reqwest(_) => println!("Reqwest error: {:?}", error),
ErrorKind::ParseIntError(_) => println!("Standard parse int error: {:?}", error),
ErrorKind::RandomResponseError(_) => println!("User defined error: {:?}", error),
_ => println!("Other error: {:?}", error),
}
}
}
Obtain backtrace of complex error scenarios
error-chain v0.12.4 Rust patterns
This recipe shows how to handle a complex error scenario and then print a backtrace. It relies on
chain_err to extend errors by appending new errors. The error stack can be unwound, thus providing a
better context to understand why an error was raised.
The below recipes attempts to deserialize the value 256 into a u8 . An error will bubble up from Serde
then csv and finally up to the user code.
use error_chain::error_chain;
#[derive(Debug, Deserialize)]
struct Rgb {
red: u8,
blue: u8,
green: u8,
}
impl Rgb {
fn from_reader(csv_data: &[u8]) -> Result<Rgb> {
let color: Rgb = csv::Reader::from_reader(csv_data)
.deserialize()
.nth(0)
.ok_or("Cannot deserialize the first CSV record")?
.chain_err(|| "Cannot deserialize RGB color")?;
Ok(color)
}
}
Ok(())
}
fn main() {
if let Err(ref errors) = run() {
eprintln!("Error level - description");
errors
.iter()
.enumerate()
.for_each(|(index, error)| eprintln!("└> {} - {}", index, error));
Run the recipe with RUST_BACKTRACE=1 to display a detailed backtrace associated with this error.
File System
Recipe Crates Categories
Read lines of strings from a file std 1.29.1 Filesystem
Avoid writing and reading from a same file same_file v1.0.6 Filesystem
File names that have been modified in the last 24 hours std 1.29.1 Filesystem OS
Recursively find all files with given predicate walkdir v2.5.0 Filesystem
Find all files with given pattern ignoring filename case glob v0.3.1 Filesystem
Read & Write
Writes a three-line message to a file, then reads it back a line at a time with the Lines iterator created by
BufRead::lines . File implements Read which provides BufReader trait. File::create opens a File
for writing, File::open for reading.
use std::fs::File;
use std::io::{Write, BufReader, BufRead, Error};
💖
let mut output = File::create(path)?;
write!(output, "Rust\n \nFun")?;
Ok(())
}
Use same_file::Handle to a file that can be tested for equality with other handles. In this example, the
handles of file to be read from and to be written to are tested for equality.
use same_file::Handle;
use std::fs::File;
use std::io::{BufRead, BufReader, Error, ErrorKind};
use std::path::Path;
if stdout_handle == handle {
return Err(Error::new(
ErrorKind::Other,
"You are reading and writing to the same file",
));
} else {
let file = File::open(&path_to_read)?;
let file = BufReader::new(file);
for (num, line) in file.lines().enumerate() {
println!("{} : {}", num, line?.to_uppercase());
}
}
Ok(())
}
cargo run
Creates a memory map of a file using memmap and simulates some non-sequential reads from the file.
Using a memory map means you just index into a slice rather than dealing with seek to navigate a File.
The Mmap::map function assumes the file behind the memory map is not being modified at the same time
by another process or else a race condition occurs.
use memmap::Mmap;
use std::fs::File;
use std::io::{Write, Error};
Gets the current working directory by calling env::current_dir , then for each entries in fs::read_dir ,
extracts the DirEntry::path and gets the metadata via fs::Metadata . The Metadata::modified returns
the SystemTime::elapsed time since last modification. Duration::as_secs converts the time to seconds
and compared with 24 hours (24 * 60 * 60 seconds). Metadata::is_file filters out directories.
Ok(())
}
Use same_file::is_same_file to detect loops for a given path. For example, a loop could be created on a
Unix system via symlinks:
mkdir -p /tmp/foo/bar/baz
ln -s /tmp/foo/ /tmp/foo/bar/baz/qux
use std::io;
use std::path::{Path, PathBuf};
use same_file::is_same_file;
fn main() {
assert_eq!(
contains_loop("/tmp/foo/bar/baz/qux/bar/baz").unwrap(),
Some((
PathBuf::from("/tmp/foo"),
PathBuf::from("/tmp/foo/bar/baz/qux")
))
);
}
Find recursively in the current directory duplicate filenames, printing them only once.
use std::collections::HashMap;
use walkdir::WalkDir;
fn main() {
let mut filenames = HashMap::new();
if *counter == 2 {
println!("{}", f_name);
}
}
}
Find JSON files modified within the last day in the current directory. Using follow_links ensures
symbolic links are followed like they were normal directories and files.
use walkdir::WalkDir;
Ok(())
}
Uses filter_entry to descend recursively into entries passing the is_not_hidden predicate thus
skipping hidden files and directories. Iterator::filter applies to each WalkDir::DirEntry even if the
parent is a hidden directory.
fn main() {
WalkDir::new(".")
.into_iter()
.filter_entry(|e| is_not_hidden(e))
.filter_map(|v| v.ok())
.for_each(|x| println!("{}", x.path().display()));
}
Recursion depth can be flexibly set by WalkDir::min_depth & WalkDir::max_depth methods. Calculates
sum of all file sizes to 3 subfolders depth, ignoring files in the root folder.
use walkdir::WalkDir;
fn main() {
let total_size = WalkDir::new(".")
.min_depth(1)
.max_depth(3)
.into_iter()
.filter_map(|entry| entry.ok())
.filter_map(|entry| entry.metadata().ok())
.filter(|metadata| metadata.is_file())
.fold(0, |acc, m| acc + m.len());
Recursively find all PNG files in the current directory. In this case, the ** pattern matches the current
directory and all subdirectories.
Use the ** pattern in any path portion. For example, /media/**/*.png matches all PNGs in media and
it's subdirectories.
use glob::glob;
Ok(())
}
Find all image files in the /media/ directory matching the img_[0-9]*.png pattern.
A custom MatchOptions struct is passed to the glob_with function making the glob pattern case
insensitive while keeping the other options Default .
use error_chain::error_chain;
use glob::{glob_with, MatchOptions};
error_chain! {
foreign_links {
Glob(glob::GlobError);
Pattern(glob::PatternError);
}
}
Ok(())
}
Hardware Support
Recipe Crates Categories
Check number of logical cpu cores num_cpus v1.16.0 Hardware support
Processor
Shows the number of logical CPU cores in current machine using [ num_cpus::get ].
fn main() {
println!("Number of logical cores is {}", num_cpus::get());
}
Memory Management
Recipe Crates Categories
Caching
Declare lazily evaluated constant lazy_static v1.4.0
Rust patterns
Constants
Declares a lazily evaluated constant HashMap . The HashMap will be evaluated once and stored behind a
global static reference.
use lazy_static::lazy_static;
use std::collections::HashMap;
lazy_static! {
static ref PRIVILEGES: HashMap<&'static str, Vec<&'static str>> = {
let mut map = HashMap::new();
map.insert("James", vec!["user", "admin"]);
map.insert("Jim", vec!["user"]);
map
};
}
fn show_access(name: &str) {
let access = PRIVILEGES.get(name);
println!("{}: {:?}", name, access);
}
fn main() {
let access = PRIVILEGES.get("James");
println!("James: {:?}", access);
show_access("Jim");
}
Networking
Recipe Crates Categories
Listen on unused port TCP/IP std 1.29.1 Net
Server
In this example, the port is displayed on the console, and the program will listen until a request is made.
SocketAddrV4 assigns a random port when setting port to 0.
OS
Run an external command passing it stdin and check for an regex v1.10.4
error code Text processing
Redirect both stdout and stderr of child process to the same std 1.29.1 OS
file
OS
Continuously process child process' outputs std 1.29.1
Text processing
Runs git log --oneline as an external Command and inspects its Output using Regex to get the hash
and message of the last 5 commits.
use std::process::Command;
use regex::Regex;
if !output.status.success() {
error_chain::bail!("Command executed with failing error code");
}
String::from_utf8(output.stdout)?
.lines()
.filter_map(|line| pattern.captures(line))
.map(|cap| {
Commit {
hash: cap[1].to_string(),
message: cap[2].trim().to_string(),
}
})
.take(5)
.for_each(|x| println!("{:?}", x));
Ok(())
}
use std::collections::HashSet;
use std::io::Write;
use std::process::{Command, Stdio};
child.stdin
.as_mut()
.ok_or("Child process stdin has not been captured!")?
.write_all(b"import this; copyright(); credits(); exit()")?;
if output.status.success() {
let raw_output = String::from_utf8(output.stdout)?;
let words = raw_output.split_whitespace()
.map(|s| s.to_lowercase())
.collect::<HashSet<_>>();
println!("Found {} unique words:", words.len());
println!("{:#?}", words);
Ok(())
} else {
let err = String::from_utf8(output.stderr)?;
error_chain::bail!("External command failed:\n {}", err)
}
}
Shows up to the 10th biggest files and subdirectories in the current working directory. It is equivalent to
running: du -ah . | sort -hr | head -n 10 .
Command s represent a process. Output of a child process is captured with a Stdio::piped between parent
and child.
use std::process::{Command, Stdio};
du_output_child.wait()?;
sort_output_child.wait()?;
println!(
"Top 10 biggest files and directories in '{}':\n{}",
directory.display(),
String::from_utf8(head_stdout.stdout).unwrap()
);
}
}
Ok(())
}
Redirect both stdout and stderr of child process to the same file
std 1.29.1 OS
Spawns a child process and redirects stdout and stderr to the same file. It follows the same idea as run
piped external commands, however process::Stdio writes to a specified file. File::try_clone
references the same file handle for stdout and stderr . It will ensure that both handles write with the
same cursor position.
The below recipe is equivalent to run the Unix shell command ls . oops >out.txt 2>&1 .
use std::fs::File;
use std::io::Error;
use std::process::{Command, Stdio};
Command::new("ls")
.args(&[".", "oops"])
.stdout(Stdio::from(outputs))
.stderr(Stdio::from(errors))
.spawn()?
.wait_with_output()?;
Ok(())
}
In Run an external command and process stdout, processing doesn't start until external Command is
finished. The recipe below calls Stdio::piped to create a pipe, and reads stdout continuously as soon as
the BufReader is updated.
The below recipe is equivalent to the Unix shell command journalctl | grep usb .
reader
.lines()
.filter_map(|line| line.ok())
.filter(|line| line.find("usb").is_some())
.for_each(|line| println!("{}", line));
Ok(())
}
use std::env;
use std::fs;
use std::io::Error;
Ok(())
}
Science
Mathematics
Recipe Crates Categories
Vector Norm ndarray v0.15.6 Science
Mathematics
Recipe Crates Categories
Vector Norm ndarray v0.15.6 Science
Adding matrices
ndarray v0.15.6 Science
Creates two 2-D matrices with ndarray::arr2 and sums them element-wise.
Note the sum is computed as let sum = &a + &b . The & operator is used to avoid consuming a and b ,
making them available later for display. A new array is created containing their sum.
use ndarray::arr2;
fn main() {
let a = arr2(&[[1, 2, 3],
[4, 5, 6]]);
println!("{}", a);
println!("+");
println!("{}", b);
println!("=");
println!("{}", sum);
}
Multiplying matrices
ndarray v0.15.6 Science
Creates two matrices with ndarray::arr2 and performs matrix multiplication on them with
ndarray::ArrayBase::dot .
use ndarray::arr2;
fn main() {
let a = arr2(&[[1, 2, 3],
[4, 5, 6]]);
println!("{}", a.dot(&b));
}
Multiply a scalar with a vector with a matrix
ndarray v0.15.6 Science
Creates a 1-D array (vector) with ndarray::arr1 and a 2-D array (matrix) with ndarray::arr2 .
First, a scalar is multiplied by the vector to get another vector. Then, the matrix is multiplied by the new
vector with ndarray::Array2::dot . (Matrix multiplication is performed using dot , while the * operator
performs element-wise multiplication.)
In ndarray , 1-D arrays can be interpreted as either row or column vectors depending on context. If
representing the orientation of a vector is important, a 2-D array with one row or one column must be used
instead. In this example, the vector is a 1-D array on the right-hand side, so dot handles it as a column
vector.
fn main() {
let scalar = 4;
Vector comparison
ndarray v0.15.6
The ndarray crate supports a number of ways to create arrays -- this recipe creates ndarray::Array s from
std::Vec using from . Then, it sums the arrays element-wise.
This recipe contains an example of comparing two floating-point vectors element-wise. Floating-point
numbers are often stored inexactly, making exact comparisons difficult. However, the
assert_abs_diff_eq! macro from the approx crate allows for convenient element-wise comparisons. To
use the approx crate with ndarray , the approx feature must be added to the ndarray dependency in
Cargo.toml . For example, ndarray = { version = "0.13", features = ["approx"] } .
This recipe also contains additional ownership examples. Here, let z = a + b consumes a and b ,
updates a with the result, then moves ownership to z . Alternatively, let w = &c + &d creates a new
vector without consuming c or d , allowing their modification later. See Binary Operators With Two
Arrays for additional detail.
use approx::assert_abs_diff_eq;
use ndarray::Array;
fn main() {
let a = Array::from(vec![1., 2., 3., 4., 5.]);
let b = Array::from(vec![5., 4., 3., 2., 1.]);
let mut c = Array::from(vec![1., 2., 3., 4., 5.]);
let mut d = Array::from(vec![5., 4., 3., 2., 1.]);
let z = a + b;
let w = &c + &d;
Vector norm
ndarray v0.15.6
This recipe demonstrates use of the Array1 type, ArrayView1 type, fold method, and dot method in
computing the l1 and l2 norms of a given vector. + The l2_norm function is the simpler of the two, as it
computes the square root of the dot product of a vector with itself. + The l1_norm function is computed by
a fold operation that sums the absolute values of the elements. (This could also be performed with
x.mapv(f64::abs).scalar_sum() , but that would allocate a new array for the result of the mapv .)
Note that both l1_norm and l2_norm take the ArrayView1 type. This recipe considers vector norms, so
the norm functions only need to accept one-dimensional views (hence ArrayView1 ). While the functions
could take a parameter of type &Array1<f64> instead, that would require the caller to have a reference to
an owned array, which is more restrictive than just having access to a view (since a view can be created from
any array or view, not just an owned array).
Array and ArrayView are both type aliases for ArrayBase . So, the most general argument type for the
caller would be &ArrayBase<S, Ix1> where S: Data , because then the caller could use &array or &view
instead of x.view() . If the function is part of a public API, that may be a better choice for the benefit of
users. For internal functions, the more concise ArrayView1<f64> may be preferable.
use ndarray::{array, Array1, ArrayView1};
fn main() {
let x = array![1., 2., 3., 4., 5.];
println!("||x||_2 = {}", l2_norm(x.view()));
println!("||x||_1 = {}", l1_norm(x.view()));
println!("Normalizing x yields {:?}", normalize(x));
}
Invert matrix
nalgebra v0.32.5 Science
use nalgebra::Matrix3;
fn main() {
let m1 = Matrix3::new(2.0, 1.0, 1.0, 3.0, 2.0, 1.0, 2.0, 1.0, 2.0);
println!("m1 = {}", m1);
match m1.try_inverse() {
Some(inv) => {
println!("The inverse of m1 is: {}", inv);
}
None => {
println!("m1 is not invertible!");
}
}
}
(De)-Serialize a Matrix
ndarray v0.15.6 Science
Serialize and deserialize a matrix to and from JSON. Serialization is taken care of by
serde_json::to_string and serde_json::from_str performs deserialization.
Note that serialization followed by deserialization gives back the original matrix.
extern crate nalgebra;
extern crate serde_json;
use nalgebra::DMatrix;
// serialize matrix
let serialized_matrix = serde_json::to_string(&matrix)?;
// deserialize matrix
let deserialized_matrix: DMatrix<i32> = serde_json::from_str(&serialized_matrix)?;
Ok(())
}
Trigonometry
Calculates the length of the hypotenuse of a right-angle triangle with an angle of 2 radians and opposite
side length of 80.
fn main() {
let angle: f64 = 2.0;
let side_length = 80.0;
fn main() {
let x: f64 = 6.0;
let a = x.tan();
let b = x.sin() / x.cos();
assert_eq!(a, b);
}
By default, Rust provides mathematical float methods such as trigonometric functions, square root,
conversion functions between radians and degrees, and so forth.
The following example computes the distance in kilometers between two points on the Earth with the
Haversine formula. Points are expressed as pairs of latitude and longitude in degrees. Then, to_radians
converts them in radian. sin , cos , powi and sqrt compute the central angle. Finally, it's possible to
calculate the distance.
fn main() {
let earth_radius_kilometer = 6371.0_f64;
let (paris_latitude_degrees, paris_longitude_degrees) = (48.85341_f64, -2.34880_f64);
let (london_latitude_degrees, london_longitude_degrees) = (51.50853_f64, -0.12574_f64);
println!(
"Distance between Paris and London on the surface of Earth is {:.1} kilometers",
distance
);
}
Complex numbers
Creates complex numbers of type num::complex::Complex . Both the real and imaginary part of the
complex number must be of the same type.
fn main() {
let complex_integer = num::complex::Complex::new(10, 20);
let complex_float = num::complex::Complex::new(10.1, 20.1);
Performing mathematical operations on complex numbers is the same as on built in types: the numbers in
question must be of the same type (i.e. floats or integers).
fn main() {
let complex_num1 = num::complex::Complex::new(10.0, 20.0); // Must use floats
let complex_num2 = num::complex::Complex::new(3.1, -4.2);
Mathematical functions
num v0.4.3 Science
Complex numbers have a range of interesting properties when it comes to how they interact with other
mathematical functions, most notibly the family of sine functions as well as the number e. To use these
functions with complex numbers, the Complex type has a few built in functions, all of which can be found
here: num::complex::Complex .
use std::f64::consts::PI;
use num::complex::Complex;
fn main() {
let x = Complex::new(0.0, 2.0*PI);
These examples calculate measures of central tendency for a data set contained within a Rust array. There
may be no mean, median or mode to calculate for an empty set of data, so each function returns an Option
to be handled by the caller.
The first example calculates the mean (the sum of all measurements divided by the number of
measurements in the set) by producing an iterator of references over the data, and using sum and len to
determine the total value and count of values respectively.
fn main() {
let data = [3, 1, 6, 1, 5, 8, 1, 8, 10, 11];
The second example calculates the median using the quickselect algorithm, which avoids a full sort by
sorting only partitions of the data set known to possibly contain the median. This uses cmp and Ordering
to succinctly decide the next partition to examine, and split_at to choose an arbitrary pivot for the next
partition at each step.
use std::cmp::Ordering;
match part {
None => None,
Some((left, pivot, right)) => {
let pivot_idx = left.len();
match pivot_idx.cmp(&k) {
Ordering::Equal => Some(pivot),
Ordering::Greater => select(&left, k),
Ordering::Less => select(&right, k - (pivot_idx + 1)),
}
},
}
}
match size {
even if even % 2 == 0 => {
let fst_med = select(data, (even / 2) - 1);
let snd_med = select(data, even / 2);
fn main() {
let data = [3, 1, 6, 1, 5, 8, 1, 8, 10, 11];
let part = partition(&data);
println!("Partition is {:?}", part);
The final example calculates the mode using a mutable HashMap to collect counts of each distinct integer
from the set, using a fold and the entry API. The most frequent value in the HashMap surfaces with
max_by_key .
use std::collections::HashMap;
fn main() {
let data = [3, 1, 6, 1, 5, 8, 1, 8, 10, 11];
Standard deviation
This example calculates the standard deviation and z-score of a set of measurements.
The standard deviation is defined as the square root of the variance (here calculated with f32's [ sqrt ],
where the variance is the sum of the squared difference between each measurement and the [ mean ],
divided by the number of measurements.
The z-score is the number of standard deviations a single measurement spans away from the [ mean ] of the
data set.
fn mean(data: &[i32]) -> Option<f32> {
let sum = data.iter().sum::<i32>() as f32;
let count = data.len();
match count {
positive if positive > 0 => Some(sum / count as f32),
_ => None,
}
}
diff * diff
}).sum::<f32>() / count as f32;
Some(variance.sqrt())
},
_ => None
}
}
fn main() {
let data = [3, 1, 6, 1, 5, 8, 1, 8, 10, 11];
Some(diff / std_deviation)
},
_ => None
};
println!("Z-score of data at index 4 (with value {}) is {:?}", data[4], zscore);
}
Miscellaneous
Big integers
num v0.4.3 Science
Calculation for integers exceeding 128 bits are possible with BigInt .
fn main() {
println!("{}! equals {}", 100, factorial(100));
}
Text Processing
Recipe Crates Categories
Collect Unicode Graphemes unicode-segmentation v1.11.0 Encoding
regex v1.10.4
Verify and extract login from an email address Text processing
lazy_static v1.4.0
regex v1.10.4
Extract a list of unique #Hashtags from a text Text processing
lazy_static v1.4.0
Filter a log file by matching multiple regular expressions regex v1.10.4 Text processing
Replace all occurrences of one text pattern with another regex v1.10.4
Text processing
pattern. lazy_static v1.4.0
Implement the FromStr trait for a custom struct std 1.29.1 Text processing
Regular Expressions
Validates that an email address is formatted correctly, and extracts everything before the @ symbol.
use lazy_static::lazy_static;
use regex::Regex;
fn main() {
assert_eq!(extract_login(r"I❤email@example.com"), Some(r"I❤email"));
assert_eq!(
extract_login(r"sdf+sdsfsd.as.sdsd@jhkk.d.rl"),
Some(r"sdf+sdsfsd.as.sdsd")
);
assert_eq!(extract_login(r"More@Than@One@at.com"), None);
assert_eq!(extract_login(r"Not an email@email"), None);
}
The hashtag regex given here only catches Latin hashtags that start with a letter. The complete twitter
hashtag regex is much more complicated.
use lazy_static::lazy_static;
use regex::Regex;
use std::collections::HashSet;
fn main() {
let tweet = "Hey #world, I just got my new #dog, say hello to Till. #dog #forever #2 #_
";
let tags = extract_hashtags(tweet);
assert!(tags.contains("#dog") && tags.contains("#forever") && tags.contains("#world"));
assert_eq!(tags.len(), 3);
}
Processes a string of text using Regex::captures_iter to capture multiple phone numbers. The example
here is for US convention phone numbers.
use regex::Regex;
use std::fmt;
struct PhoneNumber<'a> {
area: &'a str,
exchange: &'a str,
subscriber: &'a str,
}
let re = Regex::new(
r#"(?x)
(?:\+?1)? # Country Code Optional
[\s\.]?
(([2-9]\d{2})|\(([2-9]\d{2})\)) # Area Code
[\s\.\-]?
([2-9]\d{2}) # Exchange Code
[\s\.\-]?
(\d{4}) # Subscriber Number"#,
)?;
assert_eq!(
phone_numbers.map(|m| m.to_string()).collect::<Vec<_>>(),
vec![
"1 (505) 881-9292",
"1 (505) 778-2212",
"1 (505) 881-9297",
"1 (202) 991-9534",
"1 (555) 392-0011",
"1 (800) 233-2010",
"1 (299) 339-1020",
]
);
Ok(())
}
Reads a file named application.log and only outputs the lines containing “version X.X.X”, some IP
address followed by port 443 (e.g. “192.168.0.1:443”), or a specific warning.
use std::fs::File;
use std::io::{BufReader, BufRead};
use regex::RegexSetBuilder;
buffered
.lines()
.filter_map(|line| line.ok())
.filter(|line| set.is_match(line.as_str()))
.for_each(|x| println!("{}", x));
Ok(())
}
Replaces all occurrences of the standard ISO 8601 YYYY-MM-DD date pattern with the equivalent
American English date with slashes. For example 2013-01-15 becomes 01/15/2013 .
The method Regex::replace_all replaces all occurrences of the whole regex. &str implements the
Replacer trait which allows variables like $abcde to refer to corresponding named capture groups (?
P<abcde>REGEX) from the search regex. See the replacement string syntax for examples and escaping
detail.
use lazy_static::lazy_static;
use std::borrow::Cow;
use regex::Regex;
fn main() {
let before = "2012-03-14, 2013-01-15 and 2014-07-05";
let after = reformat_dates(before);
assert_eq!(after, "03/14/2012, 01/15/2013 and 07/05/2014");
}
String Parsing
Collect individual Unicode graphemes from UTF-8 string using the UnicodeSegmentation::graphemes
function from the unicode-segmentation crate.
use unicode_segmentation::UnicodeSegmentation;
fn main() {
let name = "José Guimarães\r\n";
let graphemes = UnicodeSegmentation::graphemes(name, true)
.collect::<Vec<&str>>();
assert_eq!(graphemes[3], "é");
}
Creates a custom struct RGB and implements the FromStr trait to convert a provided color hex code into
its RGB color code.
use std::str::FromStr;
#[derive(Debug, PartialEq)]
struct RGB {
r: u8,
g: u8,
b: u8,
}
Ok(RGB { r, g, b })
}
}
fn main() {
let code: &str = &r"#fa7268";
match RGB::from_str(code) {
Ok(rgb) => {
println!(
r"The RGB color code is: R: {} G: {} B: {}",
rgb.r, rgb.g, rgb.b
);
}
Err(_) => {
println!("{} is not a valid color hex code!", code);
}
}
reqwest v0.12.4
url v2.5.0
reqwest v0.12.4
Extract all unique links from a MediaWiki markup Net
regex v1.10.4
Extract the URL origin (scheme / host / port) url v2.5.0 Net
Remove fragment identifiers and query pairs from a URL url v2.5.0 Net
mime v1.3.0
Parse the MIME type of a HTTP response Net Encoding
reqwest v0.12.4
Clients
Recipe Crates Categories
Make a HTTP GET request reqwest v0.12.4 Net
reqwest v0.12.4
Query the GitHub API Net Encoding
serde v1.0.202
reqwest v0.12.4
Create and delete Gist with GitHub API Net Encoding
serde v1.0.202
reqwest v0.12.4
Consume a paginated RESTful API Net Encoding
serde v1.0.202
reqwest v0.12.4
Download a file to a temporary directory Net Filesystem
tempdir v0.3.7
Make a partial download with HTTP range headers reqwest v0.12.4 Net
Web Authentication
Recipe Crates Categories
Basic Authentication reqwest v0.12.4 Net
Extracting Links
Use reqwest::get to perform a HTTP GET request and then use Document::from_read to parse the
response into a HTML document. find with the criteria of Name is "a" retrieves all links. Call filter_map
on the Selection retrieves URLs from links that have the "href" attr (attribute).
use error_chain::error_chain;
use select::document::Document;
use select::predicate::Name;
error_chain! {
foreign_links {
ReqError(reqwest::Error);
IoError(std::io::Error);
}
}
#[tokio::main]
async fn main() -> Result<()> {
let res = reqwest::get("https://www.rust-lang.org/en-US/")
.await?
.text()
.await?;
Document::from(res.as_str())
.find(Name("a"))
.filter_map(|n| n.attr("href"))
.for_each(|x| println!("{}", x));
Ok(())
}
Call get_base_url to retrieve the base URL. If the document has a base tag, get the href attr from base
tag. Position::BeforePath of the original URL acts as a default.
Iterates through links in the document and creates a tokio::spawn task that will parse an individual link
with url::ParseOptions and Url::parse ). The task makes a request to the links with reqwest and
verifies StatusCode . Then the tasks await completion before ending the program.
use error_chain::error_chain;
use reqwest::StatusCode;
use select::document::Document;
use select::predicate::Name;
use std::collections::HashSet;
use url::{Position, Url};
error_chain! {
foreign_links {
ReqError(reqwest::Error);
IoError(std::io::Error);
UrlParseError(url::ParseError);
JoinError(tokio::task::JoinError);
}
}
#[tokio::main]
async fn main() -> Result<()> {
let url = Url::parse("https://www.rust-lang.org/en-US/")?;
let res = reqwest::get(url.as_ref()).await?.text().await?;
let document = Document::from(res.as_str());
let base_url = get_base_url(&url, &document).await?;
let base_parser = Url::options().base_url(Some(&base_url));
let links: HashSet<Url> = document
.find(Name("a"))
.filter_map(|n| n.attr("href"))
.filter_map(|link| base_parser.parse(link).ok())
.collect();
let mut tasks = vec![];
Ok(())
}
Extract all unique links from a MediaWiki markup
reqwest v0.12.4 regex v1.10.4 Net
Pull the source of a MediaWiki page using reqwest::get and then look for all entries of internal and
external links with Regex::captures_iter . Using Cow avoids excessive String allocations.
use lazy_static::lazy_static;
use regex::Regex;
use std::borrow::Cow;
use std::collections::HashSet;
use std::error::Error;
links
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
let content = reqwest::get(
"https://en.wikipedia.org/w/index.php?title=Rust_(programming_language)&action=raw",
)
.await?
.text()
.await?;
println!("{:#?}", extract_links(content.as_str()));
Ok(())
}
Uniform Resource Location
The parse method from the url crate validates and parses a &str into a Url struct. The input string
may be malformed so this method returns Result<Url, ParseError> .
Once the URL has been parsed, it can be used with all of the methods in the Url type.
Ok(())
}
A base URL includes a protocol and a domain. Base URLs have no folders, files or query strings. Each of
those items are stripped out of the given URL. PathSegmentsMut::clear removes paths and
Url::set_query removes query string.
use url::Url;
assert_eq!(base.as_str(), "https://github.com/");
println!("The base of the URL is: {}", base);
Ok(())
}
url.set_query(None);
Ok(url)
}
The join method creates a new URL from a base and relative path.
use url::{Url, ParseError};
let gh = build_github_url(path)?;
assert_eq!(gh.as_str(), "https://github.com/rust-lang/cargo");
println!("The joined URL is: {}", gh);
Ok(())
}
Ok(joined)
}
The Url struct exposes various methods to extract information about the URL it represents.
assert_eq!(url.scheme(), "ftp");
assert_eq!(url.host(), Some(Host::Domain("rust-lang.org")));
assert_eq!(url.port_or_known_default(), Some(21));
println!("The origin is as expected!");
Ok(())
}
Ok(())
}
Parses Url and slices it with url::Position to strip unneeded URL parts.
The following example shows how to parse a MIME type from a string using the mime crate.
FromStrError produces a default MIME type in an unwrap_or clause.
fn main() {
let invalid_mime_type = "i n v a l i d";
let default_mime = invalid_mime_type
.parse::<Mime>()
.unwrap_or(APPLICATION_OCTET_STREAM);
println!(
"MIME for {:?} used default value {:?}",
invalid_mime_type, default_mime
);
println!(
"MIME for {:?} was parsed as {:?}",
valid_mime_type, parsed_mime
);
}
The following example shows how to return the correct MIME type from a given filename using the mime
crate. The program will check for file extensions and match against a known list. The return value is
mime:Mime .
use mime::Mime;
fn main() {
let filenames = vec!("foobar.jpg", "foo.bar", "foobar.png");
for file in filenames {
let mime = find_mimetype(&file.to_owned());
println!("MIME for {}: {}", file, mime);
}
When receiving a HTTP reponse from reqwest the MIME type or media type may be found in the Content-
Type header. reqwest::header::HeaderMap::get retrieves the header as a
reqwest::header::HeaderValue , which can be converted to a string. The mime crate can then parse that,
yielding a mime::Mime value.
The mime crate also defines some commonly used MIME types.
Note that the reqwest::header module is exported from the http crate.
use error_chain::error_chain;
use mime::Mime;
use std::str::FromStr;
use reqwest::header::CONTENT_TYPE;
error_chain! {
foreign_links {
Reqwest(reqwest::Error);
Header(reqwest::header::ToStrError);
Mime(mime::FromStrError);
}
}
#[tokio::main]
async fn main() -> Result<()> {
let response = reqwest::get("https://www.rust-lang.org/logos/rust-logo-
32x32.png").await?;
let headers = response.headers();
match headers.get(CONTENT_TYPE) {
None => {
println!("The response does not contain a Content-Type header.");
}
Some(content_type) => {
let content_type = Mime::from_str(content_type.to_str()?)?;
let media_type = match (content_type.type_(), content_type.subtype()) {
(mime::TEXT, mime::HTML) => "a HTML document",
(mime::TEXT, _) => "a text document",
(mime::IMAGE, mime::PNG) => "a PNG image",
(mime::IMAGE, _) => "an image",
_ => "neither text nor image",
};
Ok(())
}
Clients
Recipe Crates Categories
Make a HTTP GET request reqwest v0.12.4 Net
reqwest v0.12.4
Query the GitHub API Net Encoding
serde v1.0.202
reqwest v0.12.4
Create and delete Gist with GitHub API Net Encoding
serde v1.0.202
reqwest v0.12.4
Consume a paginated RESTful API Net Encoding
serde v1.0.202
Recipe Crates Categories
reqwest v0.12.4
Download a file to a temporary directory Net Filesystem
tempdir v0.3.7
Make a partial download with HTTP range headers reqwest v0.12.4 Net
Parses the supplied URL and makes a synchronous HTTP GET request with reqwest::blocking::get .
Prints obtained reqwest::blocking::Response status and headers. Reads HTTP response body into an
allocated String using read_to_string .
use error_chain::error_chain;
use std::io::Read;
error_chain! {
foreign_links {
Io(std::io::Error);
HttpRequest(reqwest::Error);
}
}
Ok(())
}
Async
A similar approach can be used by including the tokio executor to make the main function asynchronous,
retrieving the same information.
In this example, tokio::main handles all the heavy executor setup and allows sequential code
implemented without blocking until .await .
error_chain! {
foreign_links {
Io(std::io::Error);
HttpRequest(reqwest::Error);
}
}
#[tokio::main]
async fn main() -> Result<()> {
let res = reqwest::get("http://httpbin.org/get").await?;
println!("Status: {}", res.status());
println!("Headers:\n{:#?}", res.headers());
Queries GitHub stargazers API v3 with reqwest::get to get list of all users who have marked a GitHub
project with a star. reqwest::Response is deserialized with Response::json into User objects
implementing serde::Deserialize .
[tokio::main] is used to set up the async executor and the process waits for [ reqwet::get ] to complete
before processing the response into User instances.
use serde::Deserialize;
use reqwest::Error;
#[derive(Deserialize, Debug)]
struct User {
login: String,
id: u32,
}
#[tokio::main]
async fn main() -> Result<(), Error> {
let request_url = format!("https://api.github.com/repos/{owner}/{repo}/stargazers",
owner = "rust-lang-nursery",
repo = "rust-cookbook");
println!("{}", request_url);
let response = reqwest::get(&request_url).await?;
Query the GitHub Users Endpoint using a HEAD request ( Client::head ) and then inspect the response
code to determine success. This is a quick way to query a rest resource without needing to receive a body.
reqwest::Client configured with ClientBuilder::timeout ensures a request will not last longer than a
timeout.
#[tokio::main]
async fn main() -> Result<()> {
let user = "ferris-the-crab";
let request_url = format!("https://api.github.com/users/{}", user);
println!("{}", request_url);
if response.status().is_success() {
println!("{} is a user!", user);
} else {
println!("{} is not a user!", user);
}
Ok(())
}
Creates a gist with POST request to GitHub gists API v3 using Client::post and removes it with
DELETE request using Client::delete .
The reqwest::Client is responsible for details of both requests including URL, body and authentication.
The POST body from serde_json::json! macro provides arbitrary JSON body. Call to
RequestBuilder::json sets the request body. RequestBuilder::basic_auth handles authentication. The
call to RequestBuilder::send synchronously executes the requests.
use error_chain::error_chain;
use serde::Deserialize;
use serde_json::json;
use std::env;
use reqwest::Client;
error_chain! {
foreign_links {
EnvVar(env::VarError);
HttpRequest(reqwest::Error);
}
}
#[derive(Deserialize, Debug)]
struct Gist {
id: String,
html_url: String,
}
#[tokio::main]
async fn main() -> Result<()> {
let gh_user = env::var("GH_USER")?;
let gh_pass = env::var("GH_PASS")?;
The example uses HTTP Basic Auth in order to authorize access to GitHub API. Typical use case would
employ one of the much more complex OAuth authorization flows.
Consume a paginated RESTful API
reqwest v0.12.4 serde v1.0.202 Net Encoding
Wraps a paginated web API in a convenient Rust iterator. The iterator lazily fetches the next page of results
from the remote server as it arrives at the end of each page.
use reqwest::Result;
use serde::Deserialize;
#[derive(Deserialize)]
struct ApiResponse {
dependencies: Vec<Dependency>,
meta: Meta,
}
#[derive(Deserialize)]
struct Dependency {
crate_id: String,
}
#[derive(Deserialize)]
struct Meta {
total: u32,
}
struct ReverseDependencies {
crate_id: String,
dependencies: <Vec<Dependency> as IntoIterator>::IntoIter,
client: reqwest::blocking::Client,
page: u32,
per_page: u32,
total: u32,
}
impl ReverseDependencies {
fn of(crate_id: &str) -> Result<Self> {
Ok(ReverseDependencies {
crate_id: crate_id.to_owned(),
dependencies: vec![].into_iter(),
client: reqwest::blocking::Client::new(),
page: 0,
per_page: 100,
total: 0,
})
}
self.page += 1;
let url = format!("https://crates.io/api/v1/crates/{}/reverse_dependencies?page=
{}&per_page={}",
self.crate_id,
self.page,
self.per_page);
Creates a temporary directory with tempfile::Builder and downloads a file over HTTP using
reqwest::get asynchronously.
Creates a target File with name obtained from Response::url within tempdir() and copies
downloaded data into it with io::copy . The temporary directory is automatically removed on program exit.
use error_chain::error_chain;
use std::io::copy;
use std::fs::File;
use tempfile::Builder;
error_chain! {
foreign_links {
Io(std::io::Error);
HttpRequest(reqwest::Error);
}
}
#[tokio::main]
async fn main() -> Result<()> {
let tmp_dir = Builder::new().prefix("example").tempdir()?;
let target = "https://www.rust-lang.org/logos/rust-logo-512x512.png";
let response = reqwest::get(target).await?;
use error_chain::error_chain;
use std::fs::File;
use std::io::Read;
error_chain! {
foreign_links {
HttpRequest(reqwest::Error);
IoError(::std::io::Error);
}
}
#[tokio::main]
The code then uses reqwest::blocking::Client::get to download the content in chunks of 10240
bytes, while printing progress messages. This exmple uses the synchronous reqwest module. The Range
header specifies the chunk size and position.
error_chain! {
foreign_links {
Io(std::io::Error);
Reqwest(reqwest::Error);
Header(reqwest::header::ToStrError);
}
}
struct PartialRangeIter {
start: u64,
end: u64,
buffer_size: u32,
}
impl PartialRangeIter {
pub fn new(start: u64, end: u64, buffer_size: u32) -> Result<Self> {
if buffer_size == 0 {
Err("invalid buffer_size, give a value greater than zero.")?;
}
Ok(PartialRangeIter {
start,
end,
buffer_size,
})
}
}
Basic Authentication
reqwest v0.12.4 Net
use reqwest::blocking::Client;
use reqwest::Error;
println!("{:?}", response);
Ok(())
}