Work In Progress
This book is still going through heavy edits. Pardon the dust.
What you will find here
This book is a compendium of Rust ecosystem examples and resources. It is intended to be everything you need for day-to-day Rust coding, in one place. It demonstrates good practices to accomplish common programming tasks, using the crates of the Rust ecosystem. It summarizes the language and key features of the standard library. It includes numerous links to Rust resources.
Who should read this book
This book is intended for
- new Rust programmers, to get an overview of the capabilities of the Rust ecosystem and pointers to other resources,
- experienced programmers, to find code examples and review best practices for common programming tasks.
Readers should have already some basic familiarity with Rust
⮳ concepts. The Rust book
⮳ is an excellent resource for complete beginners to get started with. This said, key features of the language are succinctly summarized in this book's language section.
Why this book
Per the curated list of Rust crates blessed.rs
⮳, "the standard library in Rust is much smaller than in Python or Go, for example. Those languages come with "batteries included" support ... Rust, on the other hand, gets things like that from the crates.io
ecosystem and the Cargo
package manager. But with more than 160 thousand crates (libraries) to choose from, a common complaint from new Rust developers is that they don't know where to start, which crates they ought to use, and which crates they ought to trust." There are no dominant frameworks or platforms akin to Rails
, Django
, Spring
or Node
in the Rust world at this time.
This book therefore intends to provide EXAMPLES to demonstrate the uses of KEY CRATES, that is libraries necessary for day-to-day Rust coding - examples which are absent from or scattered in the reference documentation⮳. It hopes to become a "cheat sheet on steroid" for the Rust ecosystem (not just for the Rust language).
This book includes most of the "Rust Cookbook"
The "Rust How-to" project started as a set of notes kept while the author was learning Rust and evolved in a standalone book. The author then came across the Rust Cookbook⮳ community project, which shares very similar goals. Unfortunately, no updates have been made to that book in more than 4 years. Many of its examples no longer work. Several crates it references are no longer maintained. The author thus decided to merge the contents of the Rust Cookbook
into this book, testing and refreshing its examples, and expanding its coverage significantly.
How to read this book
The left sidebar is organized by topic.
- The book first quickly summarizes the basics of the language and often-used elements of the standard library.
- The crates section provides pointers on how to locate key crates and provides alphabetical and categorical indices of crates used in the book.
- The bulk of the book is divided in sections named after the
crates.io
categories⮳ whenever possible. - 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 , and which categories on
crates.io
those crates belong to, like . - The book focuses on cross-cutting concerns that affect most aspects of development e.g. error handling, error customization, configuration, debugging...
- Concurrency, including asynchronous programming, is covered in details. So are development tools.
- programming domains such as CLI and Web development.
The links section provides pointers to notable Rust websites, learning resources, cheat sheets, books, and code examples...
The contributing section details how to contribute to the book itself.
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 a topic 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 easiest ways to find a specific recipe are to
- use the search button,
- scan the left-side bar for categories you are interested in,
- scan the Index of examples, and from there, click on the name of the recipe to view it.
- look up into the Word index lists concepts, crates (in lowercase), and Rust items (using their full path e.g.
parking_lot::ReentrantMutex
). - consult the alphabetical and categorical crates indices.
How to use the recipes
Recipes are designed to give you instant access to working code, along with a full explanation of what it is doing, and to guide you to further information. All recipes are self-contained programs, so that they may be copied directly into your own projects for experimentation. To do so follow the instructions below.
Consider this example for "generate random numbers within a range":
use rand::Rng; fn main() { let mut rng = rand::thread_rng(); println!("Random f64: {}", rng.r#gen::<f64>()); }
To work with it locally we can run the following commands to create a new cargo project, and change to that directory:
cargo new my-example --bin
cd my-example
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.
cargo add rand
Next 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.
A note about error handling
Error handling in Rust is robust when done correctly, but can require a fair bit of boilerplate. Because of this, one often sees Rust examples filled with unwrap
calls, instead of proper error handling.
Since this book's recipes are intended to be reused as-is and encourage best practices, they set up error handling correctly when there are Result
types involved. The structure generally looks like:
use std::net::IpAddr; use std::str; use anyhow::Result; fn main() -> Result<()> { let bytes = b"2001:db8::1"; // Bytes to string. let s = str::from_utf8(bytes)?; // String to IP address. let addr: IpAddr = s.parse()?; println!("{:?}", addr); Ok(()) }
use anyhow::Result; use url::Position; use url::Url; fn main() -> Result<()> { let parsed = Url::parse("https://httpbin.org/cookies/set?k2=v2&k1=v1")?; let cleaned: &str = &parsed[..Position::AfterPath]; println!("cleaned: {}", cleaned); Ok(()) }
In most examples, we have chosen to use anyhow
's Result
as the return type of any fallible function, instead of writing std::result::Result<_, Box<dyn std::error::Error>>
or using custom Result
/ Error
types.
Within the code, we use the ?
operator to easily propagate any error that implements the std::error::Error
trait.
For more background on error handling in Rust, read this page⮳ of the Rust book.
Additional examples
The crates/xmpl
folder in the book's GitHub repo contains additional examples that can't be embedded into the book, due to their length.
A note about crate representation
This book is intended to provide expansive coverage of "key" or "foundational" crates - those crates that make up the most common programming tasks, and that the rest of the ecosystem builds off of.
Key crates are identified by cross-referencing:
blessed.rs
⮳ and similar resources,- most downloaded crates (overall and per category) in
crates.io
⮳, - high-quality crates per
lib.rs
⮳ statistics⮳.
The selection process is necessarily opinionated. Feel free to offer suggestions (or submit a PR), if the author missed an important, widely used crate.
What other books should I consult?
Rust by Example⮳ is similar in concept - a collection of runnable examples - but not in scope, as it focuses solely on the Rust language and standard library.
Consult the links section and its books page for other recommendations.
Index of Examples
Algorithms
Randomness
| fastrand
| | |
Sorting
Recipe | Crates | Categories |
---|---|---|
Sort a vector of integers | ||
Sort a vector of floats | ||
Sort a vector of structs |
Asynchronous
Async
Recipe | Crates | Categories |
---|---|---|
Basic example | ||
Differences with other languages | ||
Which crate provides what? | ||
Async runtimes |
Async and Blocking
Async Channels
Recipe | Crates | Categories |
---|---|---|
OneShot | ||
Multiple Producer, Single Consumer |
Async Traits
Recipe | Crates | Categories |
---|---|---|
Async traits |
Futures
Recipe | Crates | Categories |
---|---|---|
Selecting futures | ||
Joining futures | ||
Map, then, either, flatten |
Streams
Recipe | Crates | Categories |
---|---|---|
Streams |
Tokio
Recipe | Crates | Categories |
---|---|---|
Basics | ||
Join | ||
Spawning | ||
IO | ||
Graceful shutdown |
Authentication
Basic Authentication
Recipe | Crates | Categories |
---|---|---|
Perform a basic HTTP authentication |
Command Line Interface
ANSI Terminal
| ansiterm
| | |
| anstream
| | |
| anstyle
| | |
| console
| | |
| owo-colors
| | |
| stylish
| | |
| termcolor
| | |
| yansi
| | |
| termion
| | |
Arguments
Recipe | Crates | Categories |
---|---|---|
Using clap's builder API | ||
Using clap 's derive API | ||
lexopt | ||
pico-args |
| structopt
| | |
TUI
Recipe | Crates | Categories |
---|---|---|
Build complex TUI |
User Interaction
Recipe | Crates | Categories |
---|---|---|
Ask for confirmation, selection, text input | ||
Display progress bars and spinners |
Command Line Utilities
Filesystem
Networking
Recipe | Crates | Categories |
---|---|---|
gping |
Shells
Compression
tar
Recipe | Crates | Categories |
---|---|---|
Compress a directory into a tarball | ||
Decompress a tarball | ||
Decompress a tarball while removing a prefix from the paths |
Concurrency
Concurrent Data Structures
Recipe | Crates | Categories |
---|---|---|
Bounded multi-producer multi-consumer queue | ||
dashmap | ||
flurry |
Crossbeam
Recipe | Crates | Categories |
---|---|---|
Spawn a short-lived thread | ||
Create a parallel pipeline | ||
Pass data between two threads |
Data Parallelism
Explicit Threads
Recipe | Crates | Categories |
---|---|---|
Use spawn, join | [] | |
Use scoped threads |
Message Passing
Recipe | Crates | Categories |
---|---|---|
Multiple producers, single consumer | ||
crossbeam-channel | ||
flume | ||
tokio |
Send
Recipe | Crates | Categories |
---|---|---|
Send and Sync traits |
Shared State
Recipe | Crates | Categories |
---|---|---|
Maintain a global mutable state | ||
Mutexes | ||
parking_lot | ||
Atomics | ||
arc-swap |
Threadpool
Recipe | Crates | Categories |
---|---|---|
Calculate the SHA256 of ISO files concurrently | ||
Draw a fractal, dispatching work to a thread pool |
Config
Configuration
Environment Variables
Cryptography
Encryption
Recipe | Crates | Categories |
---|---|---|
Salt and hash a password with PBKDF2 |
Hashing
Data Structures
Bitfield
Recipe | Crates | Categories |
---|---|---|
Define and operate on a type represented as a bitfield | ||
flagset |
Maps
Recipe | Crates | Categories |
---|---|---|
Store data in an insertion-ordered map | ||
Store data in a multimap | ||
slotmap |
Stack Allocated Arrays
UUID
Recipe | Crates | Categories |
---|---|---|
Generate and parse UUIDs |
Database
Connection Pool
Recipe | Crates | Categories |
---|---|---|
Create a connection pool |
NoSQL
Recipe | Crates | Categories |
---|---|---|
Connect to MongoDB | ||
Connect to Redis |
Postgres
Recipe | Crates | Categories |
---|---|---|
Create tables in a Postgres database | ||
Insert and query data | ||
Aggregate data | ||
tokio-postgres | ||
cornucopia for postgres | cornucopia-rs⮳ |
Query Builders Orms
Search
Recipe | Crates | Categories |
---|---|---|
Connect to Elasticsearch | ||
infisearch | ||
stork-search | ||
minisearch | ||
typesense | ||
tinysearch |
SQLite
Recipe | Crates | Categories |
---|---|---|
Create a SQLite database | ||
Insert and select data | ||
Using transactions |
Date and Time
Duration
Recipe | Crates | Categories |
---|---|---|
Measure the elapsed time between two code sections | ||
Perform checked date and time calculations | ||
Convert a local time to another timezone |
Parse
Recipe | Crates | Categories |
---|---|---|
Examine the date and time | ||
Convert date to UNIX timestamp and vice versa | ||
Display formatted date and time | ||
Parse string into DateTime struct |
Time
Recipe | Crates | Categories |
---|---|---|
Use the time crate |
Cargo
Cargo
Recipe | Crates | Categories |
---|---|---|
Basic cargo usage | ||
Cargo.toml and lock files |
Crate Registries
Recipe | Crates | Categories |
---|---|---|
Crate registries | crates.io⮳ |
Package Layout
Recipe | Crates | Categories |
---|---|---|
Package layout |
Documentation
Badges
Documentation
Recipe | Crates | Categories |
---|---|---|
Document your code | ||
Create module- or crate-level documentation |
mdBook
| mdbook-cmdrun
| | |
| Create a journal with mdbook-journal
| | |
| Check links with mdbook-linkcheck
| | |
| mdbook-pagetoc
| | |
| Hide entire chapters with mdbook-private
| | |
| Hide pages with mdbook-hide
| | |
| Create pages from a template with mdbook-tera
| | |
| mdbook-theme
| | |
| mdbook-toc
| | |
| Test code in your mdbook
| Byron-termbook | |
| Replace text in chapters with yapp
| | |
Formatting
Formatting
Recipe | Crates | Categories |
---|---|---|
Format your Rust code with rustfmt | ||
Configure rustfmt | ||
Use attributes to skip code formatting in your code |
Installation
Install
Recipe | Crates | Categories |
---|---|---|
Build and install a Rust binary with cargo install | ||
Install a Rust binary with cargo binstall |
Rustup
Recipe | Crates | Categories |
---|---|---|
Install and manage Rust toolchains with rustup |
Other
Code Build
Recipe | Crates | Categories |
---|---|---|
Save and run project-specific commands with the just command runner | ||
Check your Rust code in the background |
Code Verification
Recipe | Crates | Categories |
---|---|---|
Verify your Rust code | {{hi:kani}} |
miri
Recipe | Crates | Categories |
---|---|---|
Install the miri interpreter | ||
Detect undefined behavior with the miri interpreter |
Other
Recipe | Crates | Categories |
---|---|---|
Search for Rust APIs | roogle | |
Deploy your Rust code on shuttle.rs | shuttle.rs | |
Minimize Rust binary sizes | {{#crate }} | |
Generate Rust code | {{#crate }} |
Versioning
Versioning
Development Tools: Build Utils
Build Utils
Recipe | Crates | Categories |
---|---|---|
Compile and link statically to a bundled C library | ||
Compile and link statically to a bundled C++ library | ||
Compile a C library while setting custom defines |
Development Tools: Cargo Plugins
Auditing
Building
Recipe | Crates | Categories |
---|---|---|
cargo make | ||
Use devx | ||
Make Rust a better bash with xshell |
Code Formatting Linting
Recipe | Crates | Categories |
---|---|---|
Format your code | ||
Lint your code | ||
Fix compiler warnings automatically | ||
Format or lint your code before committing it |
Cross Compiling
Recipe | Crates | Categories |
---|---|---|
Cross-compile using zig as the linker |
Maintaining
Recipe | Crates | Categories |
---|---|---|
Edit Cargo.toml | ||
Find unused dependencies | ||
Detect dependencies that are out of date | ||
Lint your crate API changes for semver violations | ||
Manage the cargo cache |
| cargo expand
| | |
| cargo hack
| | |
Performance
Recipe | Crates | Categories |
---|---|---|
Configure your cargo project for maximum performance, fast compile times or minimal binary size | ||
cargo hakari |
Watching For Changes
Recipe | Crates | Categories |
---|---|---|
cargo watch | ||
cargo limit |
Writing
Recipe | Crates | Categories |
---|---|---|
Generate a Rust project from a template | ||
Quickly open the crates.io or docs.rs page for the latest version of a crate |
Development Tools: Debugging
Alternatives
Recipe | Crates | Categories |
---|---|---|
log | ||
slog | ||
log4rs | ||
env_logger | ||
OpenTelemetry | OpenTelemetry Rust documentation⮳ | |
OpenObserve | OpenObserve⮳ |
Config Log
Recipe | Crates | Categories |
---|---|---|
Enable log levels per module | ||
Use a custom environment variable to set up logging | ||
Include a timestamp in log messages | ||
Log messages to a custom location |
Diagnostic Functions
Recipe | Crates | Categories |
---|---|---|
Get the type name of the pointed-to value |
Log
Recipe | Crates | Categories |
---|---|---|
Log a debug message to the console | ||
Log an error message to the console | ||
Log to stdout instead of stderr | ||
Log messages with a custom logger | ||
Log to the Unix syslog |
Tracing
Recipe | Crates | Categories |
---|---|---|
Initialize the logger | ||
Enable basic tracing | ||
Combine layers | ||
Configure a custom event formatter | ||
Events | ||
Spans | ||
Add tracing spans to functions | ||
See also |
Encoding
Binary Encoders
Recipe | Crates | Categories |
---|---|---|
bincode | ||
CBOR with ciborium | ||
prost | ||
protobuf | ||
MessagePack with rmp-serde |
Complex
Recipe | Crates | Categories |
---|---|---|
Serialize and deserialize unstructured JSON | ||
Deserialize a TOML configuration file | ||
Read and write integers in little-endian byte order |
CSV
Serde
Recipe | Crates | Categories |
---|---|---|
Serialize JSON | ||
monostate | ||
serde-ignored |
Strings
Recipe | Crates | Categories |
---|---|---|
Percent-encode a string | ||
Encode a string as application/x-www-form-urlencoded | ||
Encode and decode hexadecimal | ||
Encode and decode base64 |
Typecasts
Filesystem
cwd
Recipe | Crates | Categories |
---|---|---|
Get the current working directory |
Dir
| globset
| | |
File Watching
Recipe | Crates | Categories |
---|---|---|
Watch files or directories and execute a function when they change |
Ignore
Recipe | Crates | Categories |
---|---|---|
Walk the filesystem while respecting ignore files |
Read-Write
Recipe | Crates | Categories |
---|---|---|
Read lines of strings from a file | ||
Avoid writing and reading from the same file | ||
Access a file randomly using a memory map |
Tempfile
Recipe | Crates | Categories |
---|---|---|
Create temporary files or temporary directories |
User Directories
Recipe | Crates | Categories |
---|---|---|
dirs | ||
directories |
Hardware Support
Processor
Recipe | Crates | Categories |
---|---|---|
Check the number of logical cpu cores |
Mathematics
Additional Numeric Types
Recipe | Crates | Categories |
---|---|---|
Abstract over different number types | ||
Use big integers | ||
Use big decimals | ||
Sort floats |
| num-bigint
| | |
| num
| | |
| rug
| | |
Complex Numbers
Recipe | Crates | Categories |
---|---|---|
Create complex numbers | ||
Add complex numbers | ||
Use mathematical functions on complex numbers |
Linear Algebra
Recipe | Crates | Categories |
---|---|---|
Calculate vector norms | ||
Add matrices | ||
Multiply matrices | ||
Multiply a scalar with a vector and a matrix | ||
Invert a matrix | ||
Compare vectors | ||
(De)serialize a matrix |
Statistics
Recipe | Crates | Categories |
---|---|---|
Calculate measures of central tendency | ||
Compute the standard deviation |
Trigonometry
Recipe | Crates | Categories |
---|---|---|
Calculate the side length of a triangle | ||
Verify that tan is equal to sin divided by cos | ||
Calculate the distance between two points on Earth |
Memory Management
Global Static
Recipe | Crates | Categories |
---|---|---|
Declare lazily evaluated constants |
Lazy Initialization
Recipe | Crates | Categories |
---|---|---|
std | ||
once_cell | ||
lazy_static |
OS
External
Low Level System Calls
Recipe | Crates | Categories |
---|---|---|
Call libc , the C standard library |
Rust OS
Recipe | Crates | Categories |
---|---|---|
Host containers with bottlerocket | ||
Run a Rust operating system on your computer with Redox |
Rust Patterns
Builder Pattern
Recipe | Crates | Categories |
---|---|---|
bon | ||
derive_builder | ||
typed-builder |
Design Patterns
Recipe | Crates | Categories |
---|---|---|
Implement an abstract factory | {{#crate }} | |
Clone a struct storing a boxed trait object |
| Implement the typestate pattern in Rust | {{#crate }} | |
Error Customization
Recipe | Crates | Categories |
---|---|---|
anyhow | ||
thisError | ||
miette | ||
color-eyre |
Error Handling
Functional Programming
Recipe | Crates | Categories |
---|---|---|
Compose iterators |
Rust Idioms
Recipe | Crates | Categories |
---|---|---|
Rust idioms and patterns | {{#crate }} |
Template Engine
Tera
Recipe | Crates | Categories |
---|---|---|
Create HTML files from a template |
Tinytemplate
Recipe | Crates | Categories |
---|---|---|
Create Markdown fragments from a template |
Text Editors
IDEs
Recipe | Crates | Categories |
---|---|---|
Write Rust code with VS Code | VS Code⮳ | |
Write Rust code with the zed editor |
| Use the Helix Editor | Helix editor⮳ | |
| Write Rust code with RustRover | RustRover
⮳ | |
| Use neovim
| Neovim⮳ | |
Text Processing
Regex
| Use regular expressions with backreferences and lookarounds | | |
Regex2
Recipe | Crates | Categories |
---|---|---|
Longer Regex Example |
String Concat
Recipe | Crates | Categories |
---|---|---|
Compare string concatenation methods |
String Parsing
Recipe | Crates | Categories |
---|---|---|
Collect unicode graphemes | ||
Implement the FromStr trait for a custom struct |
Web Programming
Mime
Recipe | Crates | Categories |
---|---|---|
Get a MIME type from a string | ||
Get a MIME type from a filename | ||
Parse the MIME type of a HTTP response |
Scraping
Recipe | Crates | Categories |
---|---|---|
Extract all links from the HTML of a webpage | ||
Check a webpage for broken links | ||
Extract all unique links from a MediaWiki markup |
Url
Web Programming: HTTP Client
APIs
Recipe | Crates | Categories |
---|---|---|
Query the GitHub API | ||
Check if an API resource exists | ||
Create and delete a Gist with the GitHub API | ||
Consume a paginated RESTful API | ||
Handle a rate-limited API |
Download
Recipe | Crates | Categories |
---|---|---|
Download a file to a temporary directory | ||
Make a partial download with HTTP range headers | ||
POST a file to paste.rs |
HTTP Clients
Requests
Recipe | Crates | Categories |
---|---|---|
Make a HTTP GET request | ||
Make a HTTP GET request asynchronously | ||
Set custom headers and URL parameters for a REST request |
Web Programming: HTTP Server
actix
Recipe | Crates | Categories |
---|---|---|
Create a web server with Actix Web |
axum
Recipe | Crates | Categories |
---|---|---|
Create a web server with axum |
Batteries-Included Frameworks
Recipe | Crates | Categories |
---|---|---|
loco | ||
Rust on Nails | Rust on Nails⮳ |
CORS
Recipe | Crates | Categories |
---|---|---|
Implement CORS |
GraphQL
Recipe | Crates | Categories |
---|---|---|
Create a GraphQL endpoint |
gRPC
Recipe | Crates | Categories |
---|---|---|
Implement gRPC |
hyper
Recipe | Crates | Categories |
---|---|---|
Implement an HTTP API with hyper |
Middleware
Recipe | Crates | Categories |
---|---|---|
tower | ||
tower-http | ||
Investigate alternatives to tower |
Other Frameworks
Recipe | Crates | Categories |
---|---|---|
Implement a HTTP server using rocket | ||
leptos |
Static Website Generators
Recipe | Crates | Categories |
---|---|---|
Create a simple website using a static website generator |
Contributing
API Documentation
Generate the docs.rs Documentation |
Using a Dev Container feature |
Other methods to preview the documentation HTML |
Development Editing
Dev Container Docker
Dev Environment Setup
Optional Preprocessors
Publication
Repo Structure
Topics of Interest
Crates
Language
Attributes
Attributes |
Lint attributes |
Automatically derive common traits |
Mark as must use |
Mark as deprecated |
Compile conditionally |
Closures
Control Flow
Enums
Functions
Generics
Iterators
Lifetimes
Macros
Main
Match
Modules
Ownership Borrowing
Rust Install
Simple Data Types
Slices
Structs
Traits
Trait Objects
Variables and Constants
Links
Blogs Podcasts Meetups
Books
Companies
Example Code
Learning Rust |
Rust Cheatsheets |
Comparison to other languages |
Books |
Example code |
Rust Blogs & Podcasts |
Newsletters |
Meetups |
Companies |
Lists of Rust links |
Learning
Links
Rust Cheatsheets
Standard Library
AsRef
Recipe | Crates |
---|---|
AsRef and &T |
Cow
Recipe | Crates |
---|---|
Convert Cow to &str | |
Convert Cow to String |
Derive
Hashmaps
Recipe | Crates |
---|---|
Hashmaps |
Option
Recipe | Crates |
---|---|
Option | |
Use adapters when working with references | |
Extract the value contained in Option | |
Use combinators |
Result
Recipe | Crates |
---|---|
Result |
Smart Pointers
Strings
Recipe | Crates |
---|---|
String | |
Placeholders | |
Concatenate strings |
Vectors
Recipe | Crates |
---|---|
Vec |
Rust language
Rust is a modern programming language that offers high performance, reliability, and productivity. It is designed to prevent common errors such as memory leaks, data races, and null pointer dereferences, by enforcing strict rules at compile time. Rust also supports powerful features such as generics, traits, macros, and concurrency, making it suitable for a wide range of applications.
Rust prefers snake case for variables and functions, so a method would be called read_str
instead of readStr
. For structs, traits and enums, camel case (or Pascal case) is used, for example HttpClient
.
Rust Installation
Install Rust and create a first project
- Install Rustup⮳
On WSL / Unix:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
- Check whether you have Rust installed correctly
rustc --version
cargo --version
- Open the documentation, if needed
rustup doc
- Create a new project
cargo new hello_world
cd hello_world
code . # open VS Code (or your favorite editor) and edit the code as you wish
- Build / run the code.
cargo check # check if the code can compile
cargo build # compile
cargo run # run the executable
cargo run
builds the code if cargo build
has not been invoked before or the code has changed.
Main function
use std::fs::File; use std::io::Read; use anyhow::Result; use anyhow::anyhow; fn read_uptime() -> Result<u64> { let mut uptime = String::new(); File::open("/proc/uptime")?.read_to_string(&mut uptime)?; Ok(uptime .split('.') .next() .ok_or(anyhow!("Cannot parse uptime data"))? .parse()?) } fn main() { match read_uptime() { Ok(uptime) => println!("uptime: {} seconds", uptime), Err(err) => eprintln!("error: {}", err), }; }
Async main function
use anyhow::Result; #[tokio::main] async fn main() -> Result<()> { println!("I'm async!"); Ok(()) }
Simple data types
- Integers:
i8
⮳,i16
⮳,i32
⮳,i64
⮳,i128
⮳,isize
⮳ - Unsigned:
u8
⮳,u16
⮳,u32
⮳,u128
⮳,usize
⮳ - Floating point:
f32
⮳,f64
⮳ - Boolean:
bool
⮳:true
,false
- Char:
let z: char = 'ℤ';
Unicode - Tuples:
let tup: (i32, f64, u8) = (500, 6.4, 1);
- Access via
let five_hundred = x.0;
- Destructuring via
let (x, y, z) = tup;
- Access via
- Arrays:
let a: [i32; 5] = [1, 2, 3, 4, 5];
allocated on the stack. access vialet first = a[0];
- A vector is a similar collection type provided by the standard library that is allowed to grow or shrink in size
- Unit (aka void):
()
- Type aliases:
type Kilometers = i32;
Handle overflows
- Wrap in all modes with the
wrapping_*
methods, such aswrapping_add
⮳. - Return the
std::option::Option::None
⮳ value if there is overflow with thechecked_*
methods. - Return the value and a boolean indicating whether there was overflow with the
overflowing_*
methods. - Saturate at the value’s minimum or maximum values with the
saturating_*
methods.
- table?
- add examples
Variables and Constants
fn main() { // `const` is set to a constant expression // The type must be annotated const THREE_HOURS_IN_SECONDS: u32 = 60 * 60 * 3; // ERROR: THREE_HOURS_IN_SECONDS = 10800; // Immutable variable let apples = 5; // ERROR: apples = 6; println!("apples: {}", apples); // Mutable variable let mut guess = String::new(); guess.push_str("42"); println!("guess: {}", guess); }
Shadowing
fn main() { // `x` is an immutable variable let x = 5; // ERROR: x = x +1; // But it can be redefined: let x = x + 1; println!("{x}"); // The type can change let x = "example"; println!("{x}"); }
Destructuring
fn main() { // Destructuring a tuple let (x, y, _) = (1, 2, 3); // x, y are now stored individually in two separate `i32` variables // Use _ to ignore a field you don't care about. println!("x: {x}, y: {y}"); struct Point { x: i32, y: i32, } let p = Point { x: 0, y: 7 }; // Destructuring a struct - sets a = 0 and b = 7: let Point { x: a, y: b } = p; println!("a: {a}, b: {b}"); // Here is a simpler way: let Point { x, y } = p; // This is equivalent to `let Point { x: x, y: y } = p;`` print!("x and y: {:?}", (x, y)); // An underscore can be used as well, if needed. }
Starting the name of a variable with an underscore silences unused variable warnings.
Ownership and Borrowing
Ownership
- No garbage collector. Ownership instead.
- Each value in Rust has an owner.
- There can only be one owner at a time.
fn main() { // Strings have move semantics. let s1 = String::from("hello"); // s1 is MOVED into s2 - this is NOT a shallow copy let s2 = s1; println!("{}, world!", s2); // ...but Rust has invalidated s1 // ERROR: println!("{}, world!", s1); }
When the owner goes out of scope, the value will be dropped.
fn main() { { let s = String::from("hello"); println!("{}", s); } // The `s` variable is now out of scope - Rust calls `drop` // ERROR println!("{}", s); }
Rust will never automatically create “deep” copies of your data. Use std::clone::Clone
⮳
fn main() { let s1 = String::from("hello"); let s2 = s1.clone(); // `clone` deeply copies the heap data of the `String`, // not just the stack data. println!("{s2}"); }
If a type implements the std::marker::Copy
⮳ trait (stack-only, fixed-size values, like integers, floats, and tuples thereof), variables that use it do not move, but rather are trivially copied, making them still valid after assignment to another variable.
fn main() { let x = 5; // Integer let y = x; // No MOVE println!("x = {}, y = {}", x, y); // OK }
Borrowing
Passing a variable to a function will move or copy, just as assignment does. To avoid passing a value along, borrow the value:
fn main() { let s1 = String::from("hello"); let _len = calculate_length(&s1); // `&s1` passes an immutable reference to `s1` fn calculate_length(s: &str) -> usize { s.len() } // Here, `s` goes out of scope. Because the function does not have // ownership of what it refers to, `s1` is not dropped. println!("{s1}"); }
Mutable references
// Note the `&mut` in the function's signature. fn change(some_string: &mut String) { some_string.push_str(", world"); println!("{some_string}"); } fn main() { let mut s = String::from("hello"); // note the `mut` change(&mut s); }
If you have a mutable reference to a value, you can have no other simultaneous references to that value! Functions like a read/write lock.
Memory safety
Slices
Slices
fn main() { let s = String::from("hello world"); let hello: &str = &s[0..5]; // or &s[..5]; let world = &s[6..11]; // or &s[6..]; println!("{}", hello); println!("{}", world); }
Functions
fn foo(x: i32, unit_label: char) -> i32 { let y = { let z = 3; x + z // Expression at the end of a block - no semi-colon }; println!("The value of y is: {y}{unit_label}"); y // Returns y - no semi-colon } fn main() { println!("{}", foo(1, 'm')); }
The unit type ()
(void
in some languages) is the default return type when no type is given for a function. It could be omitted: fn log(message: &str) { ... }
Generic functions
fn generic<T>(_t: T) { println!("In `generic`"); } // Explicitly specified type parameter `char` to `generic()`. // Note the turbofish notation ::<> fn main() { generic::<char>('a'); }
use std::fmt::Display; fn generic<T: ?Sized + Display>(t: &T) { // By default, generic functions will work only on types that have a // known size at compile time. Use `?Sized` to relax that rule. // `t` must be some kind of (smart) pointer: &, `Rc`, `Box`... println!("{}", t); } fn main() { let s = String::from("hello"); generic(&s[..]); }
Function pointers
fn add_one(x: i32) -> i32 { x + 1 } fn do_twice(f: fn(i32) -> i32, arg: i32) -> i32 { // function pointer f(arg) + f(arg) } fn main() { println!("{}", do_twice(add_one, 1)); }
Diverging functions
Diverging functions never return.
fn foo() -> ! { // ! is the Never type panic!("This call never returns."); } fn main() { println!("Will panic"); foo(); }
Control flow
If else
fn main() { let number = 3; let result: u8 = if number < 5 { println!("Condition was true"); 5 // `if` is an expression } else { println!("Condition was false"); 6 }; println!("{}", result); }
Also else if <cond> { ... }
Loop
fn main() { let mut counter = 0; let result = loop { counter += 1; if counter == 10 { break counter * 2; // `continue` and loop labels also exist: // https://doc.rust-lang.org/book/ch03-05-control-flow.html } }; println!("{}", result); }
While
fn main() { let mut number = 5; while number != 0 { println!("{number}!"); number -= 1; } }
For
fn main() { let a = [10, 20, 30, 40, 50]; for element in a { println!("the value is: {element}"); } // Range - generates all numbers in sequence // starting from one number and ending before another number. for number in (1..4).rev() { // reverse enumeration println!("{number}!"); } }
Structs
// We first define the struct's fields (which can be of any type). // The `derive` attribute is not required - it just enables `println!` below. #[derive(Debug)] struct User { active: bool, username: String, email: String, sign_in_count: u64, } fn main() { // We create an instance of the struct. // Note that there is no `new` or similar. let user1 = User { active: true, username: String::from("someusername123"), email: String::from("someone@example.com"), sign_in_count: 1, }; println!("{:?}", user1); }
Struct fields follow the general rule of everything being private by default unless annotated with pub
⮳.
#[derive(Debug)] struct User { active: bool, username: String, email: String, } // It is common to define a function (or an associated function, see below) that // initializes the struct: fn build_user(email: String, username: String) -> User { User { active: true, username, /* Field init shorthand, instead of writing `username: * username` */ email, // Same } } fn main() { // We create an instance of the struct: let user1: User = build_user("user@example.com".into(), "user".to_string()); // Then update the struct. // .. is used to fill in the rest let user2 = User { email: String::from("another@example.com"), ..user1 /* The remaining fields not explicitly set will have the * same value as the fields in the given instance. */ }; println!("{:?}", user2); }
// A tuple struct // Note the ( ) and the lack of field names. #[derive(Debug)] struct Color(i32, i32, i32); // A unit-like struct #[derive(Debug)] struct AlwaysEqual; // Note that there are no fields fn main() { let black = Color(0, 0, 0); println!("{black:?}"); let s = AlwaysEqual; println!("{s:?}"); }
struct Rectangle { width: u32, height: u32, } impl Rectangle { // implementation block (multiple allowed for a given struct) // Method fn area(&self) -> u32 { // short for self: &Self, an alias for the type that the impl block is // for self.width * self.height } // Associated Functions - NO self, &self, or &mut self // often use for constructors: SomeType::new(...) fn square(size: u32) -> Self { Self { width: size, height: size, } } } fn main() { let sq = Rectangle::square(5); println!("Area: {}", sq.area()); }
Enums
#[derive(Debug)] enum Message { Quit, // Unit-like variant (no fields) Move { x: i32, y: i32 }, // Struct-like variant (named fields) Write(String), // Tuple-like variant (numbered fields) ChangeColor(i32, i32, i32), // Another tuple-like variant } // Define methods on enums. impl Message { fn call(&self) { // method body would be defined here } } fn main() { // `msg` is assigned one of the variants. // Note the :: between the name of the type and the name of the variant. let msg = Message::Quit; println!("{msg:?}"); // or let msg = Message::Move { x: 10, y: 15 }; println!("{msg:?}"); // or let msg = Message::ChangeColor(127, 0, 0); println!("{msg:?}"); }
If we make an enum public, all of its variants are then public. We only need pub
⮳ before the enum
⮳ keyword.
Traits
pub trait Summary { fn summarize(&self) -> String; } pub struct NewsArticle { pub headline: String, pub location: String, pub author: String, pub content: String, } // Implement Trait on a Type impl Summary for NewsArticle { fn summarize(&self) -> String { format!("{}, by {} ({})", self.headline, self.author, self.location) } } fn main() { let na = NewsArticle { headline: "headline".to_string(), location: "location".to_string(), author: "author".to_string(), content: "...".to_string(), }; println!("Summary: {}", na.summarize()); }
Trait methods are in scope only when their trait is.
Default implementation
trait Summary { fn summarize_author(&self) -> String; // Default implementation fn summarize(&self) -> String { format!("(Read more from {}...)", self.summarize_author()) // The default implementation can call a non-default // (abstract) method } } struct Blog { author: String, } impl Summary for Blog { fn summarize_author(&self) -> String { self.author.clone() } } fn main() { let blog = Blog { author: "ferris".into(), }; println!("{}", blog.summarize()); }
Supertraits
use std::fmt; trait OutlinePrint: fmt::Display { fn outline_print(&self) { println!("* {} *", self); // We can use `println!` here, // because `self` is guaranteed to implement `Display` } } // String implements Display. That would not work otherwise. impl OutlinePrint for String {} fn main() { String::from("test").outline_print(); }
"Newtype" pattern
Unlike interfaces in languages like Java, C# or Scala, new traits can be implemented for existing types.
trait MyHash { fn myhash(&self) -> u64; } impl MyHash for i64 { fn myhash(&self) -> u64 { *self as u64 } } fn main() { let x = 1i64; println!("{}", x.myhash()); }
One restriction to note is that we can implement a trait on a type only if at least one of the trait or the type is local to our crate. If neither are, use the newtype pattern:
use std::fmt; // Tuple struct wrapping the type we want to add a non-local trait to. struct Wrapper(Vec<String>); impl fmt::Display for Wrapper { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "[{}]", self.0.join(", ")) } } // If you want the new type to have every method the inner type has, // implement the `Deref` trait instead. fn main() { println!( "{}", Wrapper(vec!["example".to_string(), "example 2".to_string()]) ); }
Traits as parameters
// Accepts any type that implements the specified trait: fn notify(item: &impl Summary) { println!("Breaking news! {}", item.summarize()); } // Trait bound syntax (mostly equivalent): fn notify2<T: Summary>(item: &T) { println!("Breaking news! {}", item.summarize()); } trait Summary { fn summarize(&self) -> String; } struct Article { txt: String, } impl Summary for Article { fn summarize(&self) -> String { self.txt.clone() } } fn main() { let a = Article { txt: String::from("Some text"), }; notify(&a); notify2(&a); }
Multiple traits
use std::clone::Clone; use std::fmt::Debug; // Note the `+` fn a_function(item: &(impl Debug + Clone)) { println!("{:?}", item.clone()); } fn some_function<T, U>(_t: &T, _u: &U) -> i32 where T: Debug + Clone, // Note the `+` U: Debug + Clone, { 42 } #[derive(Debug, Clone)] struct S; fn main() { let s = S; a_function(&s); }
Return-position impl
Trait
fn returns_closure() -> impl Fn(i32) -> i32 { |x| x + 1 } fn main() { let f = returns_closure(); println!("{}", f(1)); }
Generic traits
trait Test<T> { fn test(_t: T); } struct SomeStruct; // Note the <> in two places: impl<T> Test<T> for SomeStruct { fn test(_t: T) { println!("test"); } } fn main() { SomeStruct::test(1); SomeStruct::test(true); }
Associated types
trait Iterator { type Item; // <-- associated type // Note the use of :: to refer to the associated type fn next(&mut self) -> Option<Self::Item>; } struct MyIterator(u32); // We implement the trait for a given struct impl Iterator for MyIterator { // ...and define what associated type should be used here type Item = u32; fn next(&mut self) -> Option<Self::Item> { Some(self.0) } } fn use_iterator(it: &mut impl Iterator<Item = u32>) -> Option<u32> { it.next() } // A common pattern is a generic type (with a default) and an associated type: trait Add<Rhs = Self> { type Output; // <-- associated type fn add(self, rhs: Rhs) -> Self::Output; } fn main() { let mut it = MyIterator(42); println!("{:?}", use_iterator(&mut it)); }
Trait bounds
use std::collections::hash_map::DefaultHasher; use std::hash::Hash; use std::hash::Hasher; // Trait bounds: the `print_hash` function is generic over an unknown // type `T`, but requires that `T` implements the `Hash` trait. fn print_hash<T: Hash>(t: &T) { let mut hasher = DefaultHasher::new(); t.hash(&mut hasher); println!("The hash is {:x}", hasher.finish()); } struct Pair<A, B> { first: A, second: B, } // Generics make it possible to implement a trait conditionally. // Here, the Pair type implements Hash if, and only if, // its components do. impl<A: Hash, B: Hash> Hash for Pair<A, B> { fn hash<H: Hasher>(&self, state: &mut H) { self.first.hash(state); self.second.hash(state); } } fn main() { let p = Pair { first: 1, second: "2", }; print_hash(&p); }
Constants in traits
trait Example { const CONST_NO_DEFAULT: i32; const CONST_WITH_DEFAULT: i32 = 99; } struct S; impl Example for S { const CONST_NO_DEFAULT: i32 = 0; } fn main() { println!("{} {}", S::CONST_NO_DEFAULT, S::CONST_WITH_DEFAULT); }
Async and traits
See Async⮳
See also
Trait objects
In Rust, traits are types, but they are "unsized", which roughly means that they are only allowed to show up behind a pointer like std::boxed::Box
⮳ (which points onto the heap) or &
(which can point anywhere).
A type like &dyn ClickCallback
or Box<dyn ClickCallback>
where ClickCallback
is a Trait, is called a "trait object", and includes a pointer to an instance of a type T
implementing ClickCallback
, and a 'vtable': a pointer to T
's implementation of each method in the trait.
trait Draw { fn draw(&self); } struct Button; impl Draw for Button { fn draw(&self) { println!("Button"); } } struct Text; impl Draw for Text { fn draw(&self) { println!("Text"); } } struct Screen { components: Vec<Box<dyn Draw>>, // <-- trait object } impl Screen { fn new() -> Self { Screen { components: vec![Box::new(Button), Box::new(Text), Box::new(Text)], } } fn run(&self) { for component in self.components.iter() { // The purpose of trait objects is to permit "late binding" of // methods. Calling a method on a trait object results // in virtual dispatch at runtime. Here, `components` is // a mix of `Button` and `Text` structs. component.draw(); } } } fn main() { let s = Screen::new(); s.run(); }
The set of traits after dyn
can be made up of an object-safe⮳ base trait plus any number of autotraits (one of std::marker::Send
⮳, std::marker::Sync
⮳, std::marker::Unpin
⮳, std::panic::UnwindSafe
⮳, and std::panic::RefUnwindSafe
⮳ - see special traits⮳).
dyn Trait
dyn Trait + Send
dyn Trait + Send + Sync
dyn Trait + 'static
See also
Attributes
Attributes |
Lint attributes |
Automatically derive common traits |
Mark as must use |
Mark as deprecated |
Compile conditionally |
Attributes
Attributes can take arguments with different syntaxes:
#[attribute = "value"]
#[attribute(key = "value")]
#[attribute(value)]
#[attribute(value, value2)]
Inner attributes #![attr]
apply to the item that the attribute is declared within.
Lint attributes
During early development, place the following attributes at the top of main.rs
or lib.rs
#![allow(unused_variables)] #![allow(unused_mut)] #![allow(unused_imports)] #![allow(unused_must_use)] // or simply: #![allow(unused)] #![allow(dead_code)] #![allow(missing_docs)] // This import is not used anywhere use std::thread; // This struct is public but is not documented pub struct S; #[must_use] fn required() -> u32 { 42 } // Nothing calls this function fn dead_code() {} fn main() { // This variable is not used let x = 1; // This mutable variable is not used let mut m = 2; // The return value of this function is not used required(); println!("Done!"); }
For production-ready code, replace the above by the following, for example.
//! Crate documentation goes here. #![warn(unused, missing_debug_implementations, missing_docs, rust_2018_idioms)] // You may also add `missing_copy_implementations` if desirable. // It detects potentially-forgotten implementations of Copy for public types. // `deny` creates an error in case of violation #![deny(unreachable_pub)] // Prohibit unsafe blocks / functions // `forbid` is the same as `deny`, but also forbids changing the lint level // afterwards #![forbid(unsafe_code)] // WARNING: fn dead_code() {} // ERROR: unsafe fn unsafe_func() {} // ERROR // fn unsafe_block() { // unsafe { // } // } /// This is the required documentation for S /// We had to derive Debug to avoid a warning #[derive(Debug)] pub(crate) struct S; /// Here is the required documentation /// for the main function. fn main() { let s = S; println!("{:?}", s); }
You also apply these attributes to specific functions:
// Disables the `dead_code` lint #[allow(dead_code)] fn unused_function() {} fn main() { println!("Nobody is calling `unused_function`."); }
List of lint checks: rustc -W help
. rustc
⮳ also recognizes the tool lints for "clippy" and "rustdoc" e.g. #![warn(clippy::pedantic)]
Automatically derive common traits
See Automatic derivation.
Mark as must use
// Must use the results of the fn // Also applies to traits, structs, enums... #[must_use] fn add(a: i32, b: i32) -> i32 { a + b } fn main() { println!("{}", add(1, 2)); }
Mark as deprecated
#![allow(deprecated)] // Mark a function as deprecated // That also works for structs, enums, etc... #[deprecated(since = "5.2.0", note = "Use bar instead")] pub fn foo() { println!("foo"); } fn main() { // Use of a deprecated item foo(); // Normally we would get a warning. // In this case, we used the module-wide #![allow(deprecated)] attribute // (first line above) to suppress it. }
Compile conditionally
// This function only gets compiled if the target OS is linux #[cfg(target_os = "linux")] fn are_you_on_linux() { println!("You are running Linux!"); } // And this function only gets compiled if the target OS is *not* // linux #[cfg(not(target_os = "linux"))] fn are_you_on_linux() { println!("You are *not* running Linux!"); } fn main() { are_you_on_linux(); println!("Are you sure?"); if cfg!(target_os = "linux") { // alternative: use cfg! println!("Yes. It's definitely Linux!"); } else { println!("Yes. It's definitely *not* Linux!"); } }
See also
Generics
Generic structs
use std::fmt::Display; struct Point<T> { x: T, y: T, } impl<T> Point<T> { fn x(&self) -> &T { &self.x } } impl Point<f32> { // specify constraints on generic types fn distance_from_origin(&self) -> f32 { (self.x.powi(2) + self.y.powi(2)).sqrt() } } impl<T: Display + PartialOrd> Point<T> { // use Trait Bounds to Conditionally Implement Methods fn cmp_display(&self) { if self.x >= self.y { println!("The largest member is x = {}", self.x); } else { println!("The largest member is y = {}", self.y); } } } fn main() { let p = Point { x: 5, y: 10 }; println!("p.x = {}", p.x()); }
Lifetimes
&i32
a reference
&'a i32
a reference with an explicit lifetime
&'a mut i32
a mutable reference with an explicit lifetime
// 'static indicates that the data pointed to by the reference lives // for the _remaining_ lifetime of the running program. It can still // be coerced to a shorter lifetime. fn my_string() -> &'static str { let s: &'static str = "I have a static lifetime."; s } fn main() { println!("{}", my_string()); }
The generic lifetime 'a
will get the concrete lifetime that is equal to the smaller of the lifetimes of x
and y
:
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { if x.len() > y.len() { x } else { y } } fn main() { let (x, y) = ("short", "looooooong"); println!("{}", longest(x, y)); }
Lifetime Annotations in Struct Definitions and methods
struct ImportantExcerpt<'a> { part: &'a str, } impl ImportantExcerpt<'_> { fn level(&self) -> i32 { 3 } } fn main() { let ie = ImportantExcerpt { part: "a part" }; println!("{}", ie.level()); }
Modules
Declaring modules: In the crate root file (main.rs
or lib.rs
), you can declare new modules; say, you declare a “garden” module with mod garden;
(or pub mod garden;
for public); The compiler will look for the module’s code in these places:
- Inline, within curly brackets that replace the semicolon following mod garden
- In the file src/garden.rs
- In the file src/garden/mod.rs (older style)
In any file other than the crate root, you can declare submodules. For example, you might declare mod vegetables;
in ``src/garden.rs`. The compiler will look for the submodule’s code within the directory named for the parent module in these places:
- Inline, directly following
mod vegetables
, within curly brackets instead of the semicolon - In the file src/garden/vegetables.rs
- In the file src/garden/vegetables/mod.rs (older style)
In Rust, all items (functions, methods, structs, enums, modules, and constants) are private to parent modules by default. Items can access other items in the same module, even when private.
Items in a parent module can’t use the private items inside child modules, but items in child modules can use the items in their ancestor modules.
A clear explanation of Rust’s module system⮳
use
keyword
Create a shortcut to a path with the use
⮳ keyword once, and then use the shorter name everywhere else in the scope.
// With the following, `File` without prefix is available in the scope // For code from an external crate, the absolute path begins with the // crate name - here, the standard `std` library use std::collections::HashMap; // Glob - all public objects in `collections` are now in scope // Use sparingly use std::collections::*; // Use `as` to define aliases, for example in case of name conflict use std::fmt::Result; use std::fs::File; use std::io::Result as IoResult; // The following is equivalent to `use std::io; use std::io::Write;` use std::io::{self, Write}; // You can combine multiple `use` lines together with { } as well use std::{cmp::Ordering, fmt}; mod a { pub mod b { pub fn fn_in_b() { println!("in b!"); } } pub struct A; } // For internal code, a relative path starts from the current module and uses // `self`, or an identifier in the current module. use self::a::b; // b is now in scope // Try the simpler version: // use a::b; fn do_something() { b::fn_in_b(); } mod c { // We can construct relative paths that begin in the parent module, // rather than the current module or the crate root, by using `super` // at the start of the path. use super::a; // a is now in scope pub fn do_something_else() { let _a = a::A; println!("do_something_else"); } } mod d { pub fn let_try_this() {} } // Absolute paths start with the literal `crate`. // You can try: // use crate::d; mod e { pub mod f { pub fn try_that() { println!("try_that"); } } } // `pub use` re-exports the `f` module from the // root module, thus external code can use the path // `<crate_name>::f::try_that()` instead of // `<crate_name>::e::f::try_that()`. pub use e::f; fn main() { do_something(); c::do_something_else(); // You can of course access the item made public by `pub use` from your // module f::try_that(); }
Idiomatic - bringing the function’s parent module into scope, not the function itself:
// We bring the hosting module in scope... use front_of_house::hosting; mod front_of_house { pub mod hosting { pub fn add_to_waitlist() { println!("add_to_waitlist"); } } } fn eat_at_restaurant() { // ...then we access the function within hosting::add_to_waitlist(); } fn main() { eat_at_restaurant(); }
On the other hand, when bringing in structs, enums, and other items with use, it’s idiomatic to specify the full path.
// Bring `HashMap` in scope use std::collections::HashMap; fn main() { // We now refer to `HaspMap` without using its path let mut mymap: HashMap<u32, String> = HashMap::new(); // Let's add something to it then print... mymap.entry(42).or_insert("my favorite number".into()); println!("{:?}", mymap); }
add rust-module-system
Match, if let, while let
enum Coin { Penny, Nickel, Dime, Quarter(String), } fn value_in_cents(coin: Coin) -> u8 { match coin { Coin::Penny => 1, Coin::Nickel => 5, Coin::Dime => 10, Coin::Quarter(state) => { // pattern binding to value println!("State quarter from {:?}!", state); 25 } // if needed, use catchall _ => } } fn main() { println!("{}", value_in_cents(Coin::Penny)); }
// struct pattern matching struct Point { x: i32, y: i32, z: i32, } fn main() { let origin = Point { x: 0, y: 0, z: 0 }; match origin { // Ignoring all fields of a Point except for x by using .. Point { x, .. } => println!("x is {}", x), } }
Patterns accept 1 | 2
for or, 1..=5
for inclusive range, if x % 2 == 0
guards, @-binding Message::Hello { id: id_variable @ 3..=7,}
.
if let
fn main() { let config_max = Some(3u8); if let Some(max) = config_max { // <-- if let println!("The maximum is configured to be {}", max); } }
while let
let mut stack = Vec::new(); stack.push(1); stack.push(2); stack.push(3); while let Some(top) = stack.pop() { // <-- while let println!("{}", top); }
See also
Closures
Closures
fn find_emails(list: Vec<String>) -> Vec<String> { list.into_iter() .filter(|s| s.contains('@')) // <-- closure .collect() } fn main() { for s in find_emails(vec![ String::from("example"), String::from("example@example.com"), ]) { println!("{}", s); } }
Closure with type annotations
use std::thread; use std::time::Duration; fn main() { // closure can use type annotation. Multiple statements can be // enclosed in a block. let _expensive_closure = |num: u32| -> u32 { println!("Calculating slowly..."); thread::sleep(Duration::from_secs(2)); num }; }
Closures can capture variables
- by reference: &T
- by mutable reference: &mut T
- by value: T
They preferentially capture variables by reference and only go lower when required.
To force a move:
use std::thread; fn main() { let list = vec![1, 2, 3]; println!("Before defining closure: {:?}", list); // `move` forces the closure to take ownership of the values it uses. thread::spawn(move || println!("From thread: {:?}", list)) .join() .unwrap(); }
Closures as input parameters
// A function which takes a closure as an argument and calls it. // <F> denotes that F is a "Generic type parameter" fn apply<F>(f: F) where F: FnOnce(), { // The closure takes no input and returns nothing. // could also be `Fn` or `FnMut`. f(); } // A function which takes a closure and returns an `i32`. fn apply_to_3<F>(f: F) -> i32 where // The closure takes an `i32` and returns an `i32`. F: Fn(i32) -> i32, { f(3) } fn main() { apply(|| println!("Applied")); }
std::ops::Fn
⮳: the closure uses the captured value by reference (&T
)std::ops::FnMut
⮳: the closure uses the captured value by mutable reference (&mut T
)std::ops::FnOnce
⮳: the closure uses the captured value by value (T
)
Functions may also be used as arguments.
Iterators
fn main() { let vec1 = vec![1, 2, 3]; let vec2 = vec![4, 5, 6]; // `iter()` for vecs yields `&i32`. Destructure to `i32`. `iter()` // only borrows `vec1` and its elements, so they can be used again println!("2 in vec1: {}", vec1.iter().any(|&x| x == 2)); // `into_iter()` for vecs yields `i32`. No destructuring required. // `into_iter()` does move `vec2` and its elements, so they cannot be // used again println!("2 in vec2: {}", vec2.into_iter().any(|x| x == 2)); }
See also
Macros
The Little Book of Rust Macros⮳
fn main() { // Macro used as an expression // Define a vector let _x = vec![1, 2, 3]; // Macro used as a statement // Print the string println!("Hello!"); // Macro definition macro_rules! pat { ($i:ident) => { Some($i) }; } // Macro used in a pattern // Destructure an Option if let pat!(x) = Some(1) { assert_eq!(x, 1); } print_tuple((1, 3)); use_thread_local(); } macro_rules! Tuple { { $A:ty, $B:ty } => { ($A, $B) }; } // Macro used in a type // Define a tuple type type T2 = Tuple!(i32, i32); fn print_tuple(tupl: T2) { println!("{} {}", tupl.0, tupl.1); } fn use_thread_local() { use std::cell::RefCell; // Macro used as an item thread_local!(static FOO: RefCell<u32> = const { RefCell::new(1) }); } macro_rules! const_maker { ($t:ty, $v:tt) => { const CONST: $t = $v; }; } #[allow(dead_code)] trait T { // Macro used as an associated item const_maker! {i32, 7} } // Macro calls within macros. // // When used, the outer macro `example` is expanded, // then the inner macro `println` is expanded. macro_rules! _example { () => { println!("Macro call in a macro!") }; }
Standard Library
Option
Recipe | Crates |
---|---|
Option | |
Use adapters when working with references | |
Extract the value contained in Option | |
Use combinators |
Result
Recipe | Crates |
---|---|
Result |
Vectors
Recipe | Crates |
---|---|
Vec |
HashMap
Recipe | Crates |
---|---|
Hashmaps |
Strings
Recipe | Crates |
---|---|
String | |
Placeholders | |
Concatenate strings |
Copy-on-write
Recipe | Crates |
---|---|
Convert Cow to &str | |
Convert Cow to String |
Smart Pointers
Automatic Trait Derivation
Asref
Recipe | Crates |
---|---|
AsRef and &T |
Option
Recipe | Crates |
---|---|
Option | |
Use adapters when working with references | |
Extract the value contained in Option | |
Use combinators |
Option
Rust has no null
. Instead, use std::option::Option
⮳:
enum Option<T> {
None,
Some(T),
}
Every std::option::Option
⮳ is either std::option::Option::Some
⮳ and contains a value, or std::option::Option::None
⮳, and does not.
fn main() { let _some_number = Some(5); let absent_number: Option<i32> = None; println!("{:?}", absent_number); }
It is often used with match
⮳, if let
, or while let
:
fn bake_cake(sprinkles: Option<&str>) -> String { let mut cake = String::from("A delicious cake..."); // Add required ingredients // Handle optional sprinkles if let Some(sprinkle_choice) = sprinkles { cake.push_str( format!(" with a sprinkle of {}", sprinkle_choice).as_str(), ); } else { // sprinkles is None cake.push_str(" ready for your decorating touch!"); } cake } fn main() { print!("{}", bake_cake(Some("rainbow nonpareils"))); }
Use adapters when working with references
std::convert::AsRef
⮳ converts from&Option<T>
toOption<&T>
std::convert::AsMut
⮳ converts from&mut Option<T>
toOption<&mut T>
std::option::Option::as_deref
⮳ converts from&Option<T>
toOption<&T::Target>
std::option::Option::as_deref_mut
⮳ converts from&mut Option<T>
toOption<&mut T::Target>
Extract the value contained in Option
These methods extract the contained value in an std::option::Option
when it is the Some
variant. If the std::option::Option
⮳ is None
:
std::option::Option::expect
⮳ panics with a provided custom messagestd::option::Option::unwrap
⮳ panics with a generic messagestd::option::Option::unwrap_or
⮳ returns the provided default valuestd::option::Option::unwrap_or_default
⮳ returns the default value of the type T (which must implement thestd::default::Default
⮳ trait)std::option::Option::unwrap_or_else
⮳ returns the result of evaluating the provided function
Use combinators
use std::fs; fn read_file(filename: &str) -> Option<String> { fs::read_to_string(filename) // Convert `Result` to `Option` .ok() // `and_then` applies a function to the wrapped value if it's Some. .and_then(|contents| Some(contents.trim().to_string())) } fn main() -> anyhow::Result<()> { if !std::fs::exists("temp")? { std::fs::create_dir("temp")?; } fs::write("temp/poem.txt", b"Lorem ipsum")?; let contents = read_file("temp/poem.txt"); // Using `match` to process the returned Option. match contents { Some(poem) => println!("{}", poem), None => println!("Error reading file"), } Ok(()) }
Result
Recipe | Crates |
---|---|
Result |
Result
use std::fs::File; use std::io; use std::io::Read; use std::num::ParseIntError; // Result is a type that represents either success (Ok) or failure (Err). // pub enum Result<T, E> { // Ok(T), // Err(E), // } // Faillible functions like `File::open` return a `Result`... fn open_file(file_path: &str) { let open_result: Result<File, io::Error> = File::open(file_path); // You could handle their `Result` there and then... match open_result { Err(e) => eprintln!("An error occurred while opening the file: {}", e), Ok(file) => println!("Opened {:?}", file), } } // ...but most often you'll want to return early when encountering an error, // and propagate the error up the call stack. #[allow(clippy::question_mark)] fn read_file(file_path: &str) -> Result<String, io::Error> { let mut file = match File::open(file_path) { Ok(file) => file, Err(e) => return Err(e), }; // This said, having to use multiple `match` or `if let` is verbose // when chaining calls to faillible methods... let mut contents = String::new(); if let Err(e) = file.read_to_string(&mut contents) { Err(e) } else { Ok(contents) } } // Therefore, use the ? operator as a shortcut to return early // in case of an error. The following is equivalent to the previous function. fn read_file2(file_path: &str) -> Result<String, io::Error> { let mut file: File = File::open(file_path)?; // Note that file is of type `File`, not `io::Result<File> = Result<File, // io::Error>` let mut contents = String::new(); file.read_to_string(&mut contents)?; Ok(contents) } // You can even chain method calls immediately after the ?, e.g. // File::open(file_path)?.read_to_string(&mut contents)?; // You will often need to return one of multiple Result types. // You could create a custom error `enum` to do so: fn read_and_parse_file(file_path: &str) -> Result<i32, MyError> { let mut file = File::open(file_path)?; let mut contents = String::new(); file.read_to_string(&mut contents)?; // `read_to_string` returns `Result<_, io::Error>` let number = contents.trim().parse()?; // `parse` returns `Result<_, std::num::ParseIntError>` Ok(number) } #[allow(dead_code)] #[derive(Debug)] enum MyError { Io(io::Error), Parse(ParseIntError), } // That custom error type must implement the `From` trait. impl From<io::Error> for MyError { fn from(err: io::Error) -> MyError { MyError::Io(err) } } impl From<ParseIntError> for MyError { fn from(err: ParseIntError) -> MyError { MyError::Parse(err) } } // The `thisError` crate provides a convenient derive macro // for the standard library’s `std::error::Error` trait. // Use when writing libraries. #[allow(dead_code)] #[derive(thiserror::Error, Debug)] enum MyError2 { #[error("Io error")] Io(#[from] io::Error), #[error("Parse error")] Parse(#[from] ParseIntError), } // A simpler method than returning a custom error type // may be to return a trait object (at the cost of opacity)... fn read_and_parse_file2( file_path: &str, ) -> Result<i32, Box<dyn std::error::Error>> { let mut file = File::open(file_path)?; let mut contents = String::new(); file.read_to_string(&mut contents)?; let number = contents.trim().parse()?; Ok(number) } // ...but crates like `anyhow` can simplify error management a great deal... // `anyhow::Result<T>` is equivalent to `std::result::Result<T, anyhow::Error>`. fn read_and_parse_file3(file_path: &str) -> anyhow::Result<i32> { let mut file = File::open(file_path)?; let mut contents = String::new(); file.read_to_string(&mut contents)?; let number = contents.trim().parse()?; Ok(number) } // A function without return value in truth returns `Unit`: // `fn func() {...}` is a shorthand for `fn func() -> () { ... }` // To convert it to a faillible function, return `Result<(), SomeErrorType>`. fn unit_return_value(file_path: &str) -> anyhow::Result<()> { let _i: i32 = read_and_parse_file3(file_path)?; println!("I don't return anything!"); // Do not forget to return an Result in the happy path. // The double parentheses are required. It is `Ok( () )`. Ok(()) } // `main()` can return a `Result`, more precisely a `Result<T, E> where T: // Termination, E: Debug)`. `Result<(), Box<dyn std::error::Error>>`, // `anyhow::Result<()>` are common choices. fn main() -> Result<(), Box<dyn std::error::Error>> { let file_path = "example.txt"; open_file(file_path); // Here we ignore the `Result` return values // to avoid returning early and exercise all the code. // You should rarely need to do this. let _ = read_file(file_path); let _ = read_file2(file_path); let _ = read_and_parse_file(file_path); let _ = read_and_parse_file2(file_path); let _ = read_and_parse_file3(file_path); let _ = unit_return_value(file_path); Ok(()) }
See also
Vectors
Recipe | Crates |
---|---|
Vec |
Vec
Vectors can only store values that are the same type.
fn main() { let mut v: Vec<i32> = Vec::new(); v.push(5); v.push(6); let mut v = vec![1, 2, 3]; // or vec!(1, 2, 3) let _third: &i32 = &v[2]; // read let _third: Option<&i32> = v.get(2); for i in &v { println!("{i}"); } for i in &mut v { *i += 50; // dereference operator } }
Hashmaps
Recipe | Crates |
---|---|
Hashmaps |
All of the hashmap keys must have the same type as each other, and all of the values must have the same type.
use std::collections::HashMap; fn main() { let mut scores = HashMap::new(); scores.insert(String::from("Blue"), 10); // Update the value scores.insert(String::from("Blue"), 25); let team_name = String::from("Blue"); // Get an Option<i32> rather than an Option<&i32>, then unwrap_or to // set score to zero if scores doesn't have an entry for the key. let _score = scores.get(&team_name).copied().unwrap_or(0); // Enumerate for (key, value) in &scores { println!("{key}: {value}"); } // Adding a Key and Value Only If a Key Isn’t Present scores.entry(String::from("Yellow")).or_insert(50); }
Strings
Recipe | Crates |
---|---|
String | |
Placeholders | |
Concatenate strings |
String
fn main() { // `String` is Unicode, not ASCII let mut s1 = String::from("hello"); s1.push_str(", world!"); // `String` can be mutated s1.clear(); // Empties the String, making it equal to "" // Alternative initialization from string literals // `to_string` is available on any type that implements // the Display trait let s2 = "initial contents".to_string(); // Concatenation: note s1 has been moved here and can no longer // be used afterwards let s3 = s1 + &s2; // ERROR let s = format!("{s1}-{s2}-{s3}"); // String slice - contains the first 4 bytes of the string. let _s: &str = &s3[0..4]; // Caution: If we were to try to slice only part of a unicode // character’s bytes, Rust would panic at runtime. // Iteration for c in "Зд".chars() { println!("{c}"); } for b in "Зд".bytes() { println!("{b}"); } }
Placeholders
fn main() { let x = 5; let y = 10; println!("x = {x} and y + 2 = {}", y + 2); }
Use {:?}
to use the std::fmt::Debug
⮳ output format (annotate type with #[derive(Debug)]
) or {:#?}
for pretty print.
Also use dbg!(&rect1);
for debug output (returns ownership of the expression’s value).
Concatenate strings
Here are several common methods to concatenate Strings:
#[macro_use(concat_string)] extern crate concat_string; #[macro_use(concat_strs)] extern crate concat_strs; static DATE: &str = "2024-01-15"; static T: &str = "T"; static TIME: &str = "12:00:09Z"; fn main() { let _datetime = &[DATE, T, TIME].concat(); let _datetime = &[DATE, TIME].join(T); let _datetime = &[DATE, T, TIME].join(""); let _datetime = &[DATE, T, TIME].join(""); let list = [DATE, T, TIME]; // let _datetime: String = list.iter().map(|x| *x).collect(); let _datetime: String = list.iter().copied().collect(); let list = vec![DATE, T, TIME]; // let _datetime: String = list.iter().map(|x| *x).collect(); let _datetime: String = list.iter().copied().collect(); let _datetime = &format!("{}{}{}", DATE, T, TIME); let _datetime = &format!("{DATE}{T}{TIME}"); let mut datetime = String::new(); datetime.push_str(DATE); datetime.push_str(T); datetime.push_str(TIME); let mut datetime = Vec::<String>::new(); datetime.push(String::from(DATE)); datetime.push(String::from(T)); datetime.push(String::from(TIME)); let _datetime = datetime.join(""); let mut datetime = String::with_capacity(20); datetime.push_str(DATE); datetime.push_str(T); // or 'T' datetime.push_str(TIME); let _datetime = &(String::from(DATE) + &String::from(T) + &String::from(TIME)); let _datetime = &(String::from(DATE) + T + TIME); let _datetime = &(DATE.to_owned() + T + TIME); let _datetime = &(DATE.to_string() + T + TIME); let _datetime = concat_string!(DATE, T, TIME); let datetime = &concat_strs!(DATE, T, TIME); println!("{}", datetime); }
Examples from concatenation_benchmarks-rs⮳
Copy-on-Write
Recipe | Crates |
---|---|
Convert Cow to &str | |
Convert Cow to String |
The type std::borrow::Cow
is a smart pointer providing clone-on-write functionality.
Convert Cow
to &str
Use std::borrow::Borrow
⮳:
fn main() { use std::borrow::Borrow; let mut my_string = String::new(); let example = std::borrow::Cow::from("Example"); my_string.push_str(example.borrow()); println!("{}", my_string); }
Use std::convert::AsRef
⮳:
fn main() { let mut my_string = String::new(); let example = std::borrow::Cow::from("Example"); my_string.push_str(example.as_ref()); println!("{}", my_string); }
Use std::ops::Deref
⮳ explicitly:
fn main() { use std::ops::Deref; let mut my_string = String::new(); let example = std::borrow::Cow::from("example"); my_string.push_str(example.deref()); println!("{}", my_string); }
Use std::ops::Deref
⮳ implicitly through a coercion:
fn main() { let mut my_string = String::new(); let example = std::borrow::Cow::from("example"); my_string.push_str(&example); println!("{}", my_string); }
Convert Cow
to String
Use std::string::ToString
⮳:
fn main() { let example = std::borrow::Cow::from("example"); let s = example.to_string(); println!("{}", s); }
Use std::borrow::Cow::into_owned
⮳:
fn main() { let example = std::borrow::Cow::from("example"); println!("{}", example.into_owned()); }
Use any method to get a reference and then call std::borrow::ToOwned
⮳:
fn main() { let example = std::borrow::Cow::from("example"); println!("{}", example.as_ref().to_owned()); }
These examples were adapted from a StackOverflow discussion⮳
Smart Pointers
Rc<T>
enables multiple owners of the same data;Box<T>
andRefCell<T>
have single owners.Box<T>
allows immutable or mutable borrows checked at compile time;Rc<T>
allows only immutable borrows checked at compile time;RefCell<T>
allows immutable or mutable borrows checked at runtime.- Because
RefCell<T>
allows mutable borrows checked at runtime, you can mutate the value inside theRefCell<T>
even when theRefCell<T>
is immutable.
Box
All values in Rust are stack allocated by default. Box<T>
allow you to store data on the heap rather than the stack. What remains on the stack is the pointer to the heap data.
Boxes provide ownership for this allocation, and drop their contents when they go out of scope. Boxes also ensure that they never allocate more than isize::MAX
bytes.
The Box<T>
type is a smart pointer, because it implements the std::ops::Deref
⮳ trait, which allows Box<T>
values to be treated like a reference. You can use the dereference operator *
or deref coercion with the .
operator to retrieve its inner value.
let boxed: Box<u8> = Box::new(1); let _val: u8 = *boxed; let boxed = Box::new("example"); // Deref coercion: equivalent to (*boxed.deref()).len() let _val = boxed.len();
Use Box<T>
when
- you have a dynamically sized type, whose size can’t be known at compile time,
- you want to own a value and you care only that it’s a type that implements a particular trait rather than being of a specific type,
- you don't want to rely on stack space.
// Define a Node struct to represent a single element in a linked list. struct Node { value: i32, // Node is a recursive data type. next: Option<Box<Node>>, } impl Node { fn new(value: i32) -> Self { Node { value, next: None } } // Recursively traverses the list until it finds the last node // (where next is None) and sets its next field to a new Node. fn append(&mut self, value: i32) { match self.next { Some(ref mut next_node) => next_node.append(value), None => self.next = Some(Box::new(Node::new(value))), } } fn print(&self) { print!("{}", self.value); if let Some(ref next_node) = self.next { print!(" -> "); next_node.print(); } else { println!(); } } } fn main() { // The linked list has an unknown number of nodes, // thus its size is not fixed. // It could not be stored directly on the stack. // By using `Box`, which pointer to the heap has a defined size, // we can create the `head` local variable on the stack. let mut head = Node::new(1); head.append(2); head.append(3); head.append(4); head.print(); // Output: 1 -> 2 -> 3 -> 4 }
Rc
The Rc<T>
type keeps track of the number of references to data on the heap so that data can have multiple owners.
fn main() { todo!(); }
RefCell
The RefCell<T>
type with its interior mutability gives us a type that we can use when we need an immutable type but need to change an inner value of that type; it also enforces the borrowing rules at runtime instead of at compile time.
use std::cell::RefCell; // The `RefCell` type in Rust is used for _interior mutability_, // a pattern that allows you to mutate data even when there are immutable // references to it. `RefCell` dynamically borrow checks at runtime, // unlike Rust's standard borrowing rules (checks at compile time). // Attempts to violate borrowing rules (like having multiple mutable borrows) // will cause a panic at runtime. `RefCell` is single-threaded. // The corresponding `Sync` version of `RefCell<T>` is `RwLock<T>`. fn main() { // Create a RefCell containing a vector of integers let data = RefCell::new(vec![1, 2, 3, 4, 5]); // Borrow the data immutably { let borrowed_data = data.borrow(); println!("Borrowed (immutable): {:?}", borrowed_data); } // The immutable borrow ends here // Borrow the data mutably and modify it { let mut borrowed_data = data.borrow_mut(); borrowed_data.push(6); println!("Borrowed (mutable): {:?}", borrowed_data); } // The mutable borrow ends here // Borrow the data immutably again to check the modification { let borrowed_data = data.borrow(); println!("Borrowed (immutable again): {:?}", borrowed_data); } }
AsRef and &T
Recipe | Crates |
---|---|
AsRef and &T |
When and why to use AsRef
fn main() { todo!(); }
Automatic trait derivation
The derive
⮳ attribute generates code that will implement a trait with its own default implementation on the type you’ve annotated with the derive syntax.
// On structs #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash, Default)] struct S(i32); fn main() { println!("{:?}", S(0)); println!("{}", S(1) == S(1)); }
You can use the cargo_expand
utility to see the exact code that is generated for your specific type.
See also:
Derive More
Derive More (crates)⮳ derive lots of additional, commonly used traits and static methods for both structs and enums.
use derive_more::Add;
use derive_more::Display;
use derive_more::From;
use derive_more::Into;
#[derive(PartialEq, From, Add)]
struct MyInt(i32);
#[derive(PartialEq, From, Into)]
struct Point2D {
x: i32,
y: i32,
}
#[derive(PartialEq, From, Add, Display)]
enum MyEnum {
#[display("int: {}", _0)]
Int(i32),
Uint(u32),
#[display("nothing")]
Nothing,
}
fn main() {
assert!(MyInt(11) == MyInt(5) + 6.into());
assert!((5, 6) == Point2D { x: 5, y: 6 }.into());
assert!(MyEnum::Int(15) == (MyEnum::Int(8) + 7.into()).unwrap());
assert!(MyEnum::Int(15).to_string() == "int: 15");
assert!(MyEnum::Uint(42).to_string() == "42");
assert!(MyEnum::Nothing.to_string() == "nothing");
}
Key crates
Consult the following sites for crate recommendations:
blessed.rs
⮳lib.rs
⮳lib.rs
stats⮳- The Rust community's official crate registry:
crates.io
⮳ crates.io
's most (recent) downloads⮳
or older resources, such as:
Crates
Crates mentioned in this book, by alphabetic order.
A
B
C
D
E
F
G
H
I
J
K
L
M
N
O
P
Q
R
S
T
U
W
X
Y
Z
Crates by category
Algorithms
Rust implementations of core algorithms, such as hashing, sorting, searching, and more.
Random Numbers
| fastrand
| | |
Sorting
Recipe | Crates | Categories |
---|---|---|
Sort a vector of integers | ||
Sort a vector of floats | ||
Sort a vector of structs |
Generate Random Values
| fastrand
| | |
Generate random numbers
Generates random numbers with help of the random-number generator rand::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(); // Note: we use a raw identifier to stay compatible with Rust edition 2024. // `gen` is a keyword in the 2024 edition. let n1: u8 = rng.r#gen(); let n2: u16 = rng.r#gen(); println!("Random u8: {}", n1); println!("Random u16: {}", n2); println!("Random u32: {}", rng.r#gen::<u32>()); println!("Random i32: {}", rng.r#gen::<i32>()); println!("Random float: {}", rng.r#gen::<f64>()); }
Generate random numbers within a range
Generates a random value within half-open [0, 10)
range (not including 10
) with rand::Rng::gen_range
⮳ 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)); }
rand::distributions::uniform::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; use rand::distributions::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; } } }
Generate random numbers within a given distribution
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 rand::distributions::Distribution::sample
⮳ with help of a random-number generator rand::Rng
⮳. The distributions available are documented here⮳. An example using the rand_distr::Normal
⮳ distribution is shown below.
use rand::thread_rng; use rand_distr::Distribution; use rand_distr::Normal; use rand_distr::NormalError; fn main() -> Result<(), NormalError> { let mut rng = thread_rng(); let normal = Normal::new(2.0, 3.0)?; let v = normal.sample(&mut rng); println!("{} is from a N(2, 9) distribution", v); Ok(()) }
Generate random values of a custom type
Randomly generates a tuple (i32, bool, f64)
and variable of user defined type Point
. Implements the rand::distributions::Distribution
⮳ trait on type Point for rand::distributions::Standard
⮳ trait in order to allow random generation.
use rand::Rng; use rand::distributions::Distribution; use rand::distributions::Standard; #[derive(Debug)] #[allow(dead_code)] struct Point { x: i32, y: i32, } impl Distribution<Point> for Standard { fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Point { let (rand_x, rand_y) = rng.r#gen(); Point { x: rand_x, y: rand_y, } } } fn main() { let mut rng = rand::thread_rng(); let rand_tuple = rng.r#gen::<(i32, bool, f64)>(); let rand_point: Point = rng.r#gen(); println!("Random tuple: {:?}", rand_tuple); println!("Random Point: {:?}", rand_point); }
Create random passwords from a set of alphanumeric characters
Randomly generates a string of given length ASCII characters in the range A-Z, a-z, 0-9
, with rand::distributions::Alphanumeric
⮳ sample.
use rand::Rng; use rand::distributions::Alphanumeric; use rand::thread_rng; fn main() { let rand_string: String = thread_rng() .sample_iter(&Alphanumeric) .take(30) .map(char::from) .collect(); println!("{}", rand_string); }
Create random passwords from a set of user-defined characters
Randomly generates a string of given length ASCII characters with custom user-defined bytestring, with rand::Rng::gen_range
.
fn main() { use rand::Rng; const CHARSET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ\ abcdefghijklmnopqrstuvwxyz\ 0123456789)(*&^%$#@!~"; const PASSWORD_LEN: usize = 30; let mut rng = rand::thread_rng(); let password: String = (0..PASSWORD_LEN) .map(|_| { let idx = rng.gen_range(0..CHARSET.len()); CHARSET[idx] as char }) .collect(); println!("{:?}", password); }
fastrand
A simple and fast random number generator. No dependencies, non-cryptographically secure random numbers, lower complexity than rand
Sorting Vectors
Recipe | Crates | Categories |
---|---|---|
Sort a vector of integers | ||
Sort a vector of floats | ||
Sort a vector of structs |
Sort a vector of integers
This example will sort a Vector of integers via std::vec::Vec::sort
⮳. Alternative would be to use std::vec::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(); assert_eq!(vec, vec![1, 2, 5, 10, 15]); println!("{:?}", vec); }
Sort a vector of floats
A vector of f32 or f64 can be sorted with sort_by
and std::cmp::PartialOrd::partial_cmp
⮳.
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()); assert_eq!(vec, vec![1.1, 1.123, 1.15, 2.0, 5.5]); println!("{:?}", vec); }
Sort a vector of structs
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 std::cmp::Eq
⮳, std::cmp::PartialEq
⮳, std::cmp::Ord
⮳ and std::cmp::PartialOrd
⮳. These traits can be simply derived. You can also provide a custom comparator function using a std::vec::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), ]; // Sort people by derived natural order (Name and age) people.sort(); assert_eq!(people, vec![ Person::new("Al".to_string(), 60), Person::new("John".to_string(), 1), Person::new("Zoe".to_string(), 25), ]); println!("{:?}", people); // Sort people by age people.sort_by(|a, b| b.age.cmp(&a.age)); assert_eq!(people, vec![ Person::new("Al".to_string(), 60), Person::new("Zoe".to_string(), 25), Person::new("John".to_string(), 1), ]); println!("{:?}", people); }
See also
glidesort⮳ is a Rust implementation of Glidesort, a stable adaptive quicksort/mergesort hybrid sorting algorithm.
Asynchronous programming
Crates to help you deal with events independently of the main program flow, using techniques like futures, promises, waiting, or eventing.
Recipe | Crates | Categories |
---|---|---|
Basic example | ||
Differences with other languages | ||
Which crate provides what? | ||
Async runtimes |
Recipe | Crates | Categories |
---|---|---|
Selecting futures | ||
Joining futures | ||
Map, then, either, flatten |
Recipe | Crates | Categories |
---|---|---|
Basics | ||
Join | ||
Spawning | ||
IO | ||
Graceful shutdown |
Recipe | Crates | Categories |
---|---|---|
OneShot | ||
Multiple Producer, Single Consumer |
Recipe | Crates | Categories |
---|---|---|
Async traits |
Recipe | Crates | Categories |
---|---|---|
Streams |
Async
Recipe | Crates | Categories |
---|---|---|
Basic example | ||
Differences with other languages | ||
Which crate provides what? | ||
Async runtimes |
Asynchronous programming, or async for short, is a concurrent programming model supported by an increasing number of programming languages. It lets you run a large number of concurrent tasks, while preserving much of the look and feel of ordinary synchronous programming, through the async/await syntax. It helps you deal with events independently of the main program flow, using techniques like futures, promises, waiting, or eventing.
- Ability to make progress on multiple tasks, even if they don't execute at the exact same time.
- Mechanism: cooperative multitasking - tasks yield control, allowing other tasks to run.
- Involves context switching on a single thread or, most often, among a few threads (the pool of which is opaquely managed by the async runtime).
- Achieves non-blocking I/O operations to improve responsiveness and efficiency.
- Lower overhead compared to multithreading.
- Multi-threaded async programming also requires careful synchronization to prevent data races.
Key constructs in Rust:
async
⮳ /await
⮳ keywordsstd::future::Future
⮳
Basic example
use std::future::Future; struct SomeStruct; // Most often, we will use async functions. // Rust transforms the `async fn` at compile time into a state machine // that _implicitly_ returns a `Future`. A future represents an // asynchronous computation that might not have finished yet. async fn first_task() -> SomeStruct { // ... println!("First task"); SomeStruct } async fn second_task_1(_s: &SomeStruct) { // ... println!("Second task, part 1"); } // `async fn` is really syntaxic sugar for a function... #[allow(clippy::manual_async_fn)] fn second_task_2() -> impl Future<Output = ()> { // ...that contains an `async` block. async { println!("Second task, part 2"); } // returns `Future<Output = ()>` } async fn do_something() { // Use `.await` to start executing the future. let s = first_task().await; // `await` yields control back to the executor, which may decide to do // other work if the task is not ready, then come back here. // `join!` is like `.await` but can wait for multiple futures // concurrently, returning when all branches complete. let f1 = second_task_1(&s); let f2 = second_task_2(); futures::join!(f1, f2); // or tokio::join! } // We replace `fn main()` by `async fn main()` and declare which // executor runtime we'll use - in this case, Tokio. The runtime crate // must be added to `Cargo.toml`: `tokio = { version = "1", features = // that transforms it into a synchronous fn main() that initializes a // runtime instance and executes the async main function. #[tokio::main] async fn main() { do_something().await; // note: `await` must be called or nothing is executing. // Futures are lazy }
As any form of cooperative multitasking, a future that spends a long time without reaching an await
⮳ "blocks the thread", which may prevent other tasks from running.
Differences with other languages
Rust's implementation of async
⮳ differs from most languages in a few ways:
- Rust's
async
⮳ operations are lazy. Futures are inert in Rust and only make progress only when polled. The executor calls thestd::task::Poll
⮳ method repeatedly to execute futures.
async fn say_world() { println!("world"); } #[tokio::main] async fn main() { // Calling `say_world()` does not execute the body of `say_world()`. let op = say_world(); // This println! comes first println!("hello"); // Calling `.await` on `op` starts executing `say_world`. op.await; } // Prints: // hello // world // Example from https://tokio.rs/tokio/tutorial/hello-tokio
- Dropping a future stops it from making further progress.
- Async is zero-cost in Rust. You can use
async
⮳ without heap allocations and dynamic dispatch. This also lets you use async in constrained environments, such as embedded systems. - No built-in runtime is provided by Rust itself. Instead, runtimes are provided by community-maintained crates.
- Both single- and multi-threaded runtimes are available.
Which crate provides what?
- The
async
⮳ /await
⮳ syntactic sugar is supported directly by the Rust compiler. - The most fundamental traits, types, and functions, such as the
std::future::Future
⮳ trait, are provided by the standard library. - Many utility types, macros and functions are provided by the
futures
⮳ crate. They can be used in any async Rust application. - Execution of async code, IO and task spawning are provided by "async runtimes", such as
tokio
⮳ andasync_std
⮳. Most async applications, and some async crates, depend on a specific runtime.
Async runtimes
In most cases, prefer the tokio
runtime - see The State of Async Rust: Runtimes⮳.
Alternatives to the Tokio async ecosystem include:
- ⮳: async version of the Rust standard library. No longer maintained?
- Smol⮳
- Embassy⮳ for embedded systems.
- Mio⮳ is a fast, low-level I/O library for Rust focusing on non-blocking APIs and event notification for building high performance I/O apps with as little overhead as possible over the OS abstractions. It is part of the Tokio ecosystem.
See also
Asynchronous Programming in Rust (book)⮳
Async traits
Recipe | Crates | Categories |
---|---|---|
Async traits |
As of Rust 1.75, it is possible to have async
⮳ functions in traits:
struct MyHealthChecker; trait HealthCheck { async fn check(&mut self) -> bool; // <- async fn defined in a Trait } impl HealthCheck for MyHealthChecker { async fn check(&mut self) -> bool { // async fn implementation in the associated impl block do_async_op().await } } async fn do_health_check(mut hc: impl HealthCheck) { if !hc.check().await { // use as normal log_health_check_failure().await; } else { println!("Health check was normal"); } } async fn do_async_op() -> bool { true } async fn log_health_check_failure() { println!("Health check failure"); } #[tokio::main] async fn main() { let hc = MyHealthChecker; do_health_check(hc).await; }
Stabilizing async fn in traits in 2023⮳
This is in turn enabled by return-position impl Trait
in traits, since async fn
is sugar for functions that return -> impl Future
.
#[allow(dead_code)] trait Container { // Return `Impl` in a trait fn items(&self) -> impl Iterator<Item = u8>; } struct MyContainer { items: Vec<u8>, } impl Container for MyContainer { fn items(&self) -> impl Iterator<Item = u8> { self.items.iter().cloned() } } fn main() { let c = MyContainer { items: vec![1, 2, 3], }; for i in c.items { println!("{}", i); } }
Note that there are still caveats for public traits - see Announcing async fn
and return-position impl Trait
in traits⮳.
In addition, traits that use -> impl Trait
and async fn
are not object-safe, which means they lack support for dynamic dispatch. In the meanwhile, use the async-trait
crate.
use async_trait::async_trait; #[async_trait] trait Advertisement { async fn run(&self); } struct Modal; #[async_trait] impl Advertisement for Modal { async fn run(&self) { self.render_fullscreen().await; for _ in 0..4u16 { remind_user_to_join_mailing_list().await; } self.hide_for_now().await; } } impl Modal { async fn render_fullscreen(&self) { println!("Render fullscreen"); } async fn hide_for_now(&self) { println!("Hide for now"); } } async fn remind_user_to_join_mailing_list() { println!("Please join our mailing list"); } #[tokio::main] async fn main() { Modal.run().await; }
async-trait = Provides a workaround for the lack of language support for async functions in traits
Tokio
Recipe | Crates | Categories |
---|---|---|
Basics | ||
Join | ||
Spawning | ||
IO | ||
Graceful shutdown |
Basics
Tokio is an asynchronous runtime for the Rust programming language. It provides the building blocks needed for writing networking applications. Tokio provides a few major components:
-
Multiple variations of the runtime for executing asynchronous code. Everything from a multi-threaded, work-stealing runtime to a light-weight, single-threaded runtime.
-
An asynchronous version of the standard library.
-
A large ecosystem of libraries.
-
creating and running a runtime, spawning tasks, working with I/O and timers, and handling errors.
Join
By running all async expressions on the current task, the expressions are able to run concurrently but not in parallel. This means all expressions are run on the same thread and if one branch blocks the thread, all other expressions will be unable to continue. If parallelism is required, spawn each async expression using tokio::spawn
and pass the join handle to join!
.
Spawning
IO
- read and write data asynchronously with Tokio, using streams, codecs, and futures. It also shows how to handle errors and timeouts.
equivalent to
fn main() { tokio::runtime::Builder::new_current_thread() .enable_all() .build() .unwrap() .block_on(async { println!("Hello world"); }) }
In some cases, it is necessary to run one or more futures that do not implement Send and thus are unsafe to send between threads. In these cases, a local task set may be used to schedule one or more !Send
futures to run together on the same thread.
use std::rc::Rc; use tokio::task; use tokio::time; #[tokio::main] async fn main() { // Data that is not thread-safe: let nonsend_data = Rc::new("world"); // A set of tasks which are executed on the same thread: let local = task::LocalSet::new(); let nonsend_data2 = nonsend_data.clone(); local.spawn_local(async move { // ... println!("hello {}", nonsend_data2) }); local.spawn_local(async move { time::sleep(time::Duration::from_millis(100)).await; println!("goodbye {}", nonsend_data) }); // ... local.await; }
Graceful shutdown
Example from c-tokio_graceful_shutdownc-tokio_graceful_shutdown⮳:
use tokio::time::Duration;
use tokio::time::sleep;
use tokio_graceful_shutdown::SubsystemBuilder;
use tokio_graceful_shutdown::SubsystemHandle;
use tokio_graceful_shutdown::Toplevel;
async fn countdown() {
for i in (1..=3).rev() {
tracing::info!("Shutting down in: {}", i);
sleep(Duration::from_millis(1000)).await;
}
}
async fn countdown_subsystem(
subsys: SubsystemHandle,
) -> std::result::Result<(), Box<dyn std::error::Error + Send + Sync>> {
tokio::select! {
_ = subsys.on_shutdown_requested() => {
tracing::info!("Countdown cancelled.");
},
_ = countdown() => {
subsys.request_shutdown();
}
};
Ok(())
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
// Init logging
tracing_subscriber::fmt().init();
// Setup and execute subsystem tree
Toplevel::new(|s| async move {
s.start(SubsystemBuilder::new("Countdown", countdown_subsystem));
})
// Signals the Toplevel object to listen for SIGINT/SIGTERM/Ctrl+C
.catch_signals()
// Collects all the return values of the subsystems, determines the global error state
.handle_shutdown_requests(Duration::from_millis(1000))
.await
.map_err(|e| e.into())
}
Useful links
tokio.rs
- tokio-rs
async-stream
- tokio-rs
mio
tokio
glossary⮳tokio
tutorial⮳- Tokio "mini-Redis" example:
- Template for a tokio-rs app with logging & command line argument parser:
Channels for use in async code
Recipe | Crates | Categories |
---|---|---|
OneShot | ||
Multiple Producer, Single Consumer |
The most common form of synchronization in an async program is message passing. Two tasks operate independently and send messages to each other to synchronize. Doing so has the advantage of avoiding shared state. Message passing is implemented using async channels.
Tokio's sync
⮳ module provides channels that work well with async code.
OneShot
tokio::sync::oneshot
⮳ sends a single value from a single producer to a single consumer. This channel is usually used to send the result of a computation to a waiter.
use tokio::sync::oneshot; async fn some_computation(input: u32) -> String { format!("the result of computation is {}", input) } async fn one_shot() { let (tx, rx) = oneshot::channel(); tokio::spawn(async move { let res = some_computation(0).await; tx.send(res).unwrap(); // Alternatively, return the value via the joinhandle returned // by `spawn` }); // Do other work while the computation is happening in the background // Wait for the computation result let res = rx.await.unwrap(); println!("{}", res); } #[tokio::main] async fn main() { one_shot().await; }
Another example:
use std::time::Duration; use tokio::sync::oneshot; async fn download_file() -> Result<String, std::io::Error> { // Simulate downloading a file let filename = "data.txt"; tokio::time::sleep(Duration::from_secs(2)).await; println!("Downloaded file: {}", filename); Ok(filename.to_owned()) } async fn process_file(filename: String) { // Simulate processing the downloaded file println!("Processing file: {}", filename); tokio::time::sleep(Duration::from_secs(1)).await; println!("Finished processing file."); } async fn async_main() -> Result<(), Box<dyn std::error::Error>> { let (sender, receiver) = oneshot::channel(); // Spawn the download task tokio::spawn(async move { let filename = download_file().await?; sender.send(filename).expect("Failed to send filename"); Ok::<(), std::io::Error>(()) }); // Wait for the downloaded filename from the receiver let filename = receiver.await?; // Spawn the processing task with the filename tokio::spawn(async move { process_file(filename).await; }); Ok(()) } fn main() { let rt = tokio::runtime::Runtime::new().unwrap(); rt.block_on(async { async_main().await }).unwrap(); }
Send messages from multiple producers to a single consumer
use tokio::sync::mpsc; async fn some_computation(input: u32) -> String { format!("the result of computation is {}", input) } pub async fn multi_producer_single_receiver() { let (tx, mut rx) = mpsc::channel(100); tokio::spawn(async move { for i in 1..=10 { let res = some_computation(i).await; tx.send(res).await.unwrap(); } }); while let Some(res) = rx.recv().await { println!("{}", res); } } #[tokio::main] async fn main() { multi_producer_single_receiver().await; }
Send messages from multiple producers to one of multiple consumers
async-channel
offers two kinds of async multi-producer multi-consumer channel, where each message can be received by only one of all existing consumers.
- Bounded channel with limited capacity,
- Unbounded channel with unlimited capacity.
The Sender and Receiver sides are cloneable and can be shared among multiple threads.
When all Senders or all Receivers are dropped, the channel becomes closed. When a channel is closed, no more messages can be sent, but remaining messages can still be received. The channel can also be closed manually by calling Sender::close()
or Receiver::close()
.
use async_channel::Receiver; use async_channel::Sender; use async_channel::TryRecvError; use async_channel::bounded; use rand::Rng; use tokio::task; use tokio::time; use tokio::time::Duration; async fn producer(id: usize, tx: Sender<String>) { for i in 0..5 { let msg = format!("Producer {}: Message {}", id, i); // Sends messages to the channel. // It creates messages in a loop, sends them to the channel. // If the channel is full, this method awaits until there is space for a // message. if let Err(err) = tx.send(msg).await { // The channel is closed. eprintln!("Failed to send message: {}", err); break; } // Simulate work let sleep_duration = rand::thread_rng().gen_range(10..50); time::sleep(Duration::from_millis(sleep_duration)).await; } } async fn consumer(id: usize, rx: Receiver<String>) { // Receives a message from the channel. // If the channel is empty, awaits until there is a message. // If the channel is closed, receives a message or returns an error if there // are no more messages. while let Ok(msg) = rx.recv().await { println!("Consumer {}: Received {}", id, msg); // Simulate processing let sleep_duration = rand::thread_rng().gen_range(30..100); time::sleep(Duration::from_millis(sleep_duration)).await; } assert_eq!(rx.try_recv(), Err(TryRecvError::Closed)); } #[tokio::main] async fn main() { let (tx, rx) = bounded(2); // Create a bounded channel with a capacity of 2 // You may also use an unbounded queue. assert_eq!(rx.try_recv(), Err(TryRecvError::Empty)); // Create 3 producer tasks let mut producer_tasks = vec![]; for i in 0..3 { let tx = tx.clone(); producer_tasks.push(task::spawn(producer(i, tx))); } assert_eq!(tx.sender_count(), 4); // Create 2 consumer tasks let mut consumer_tasks = vec![]; for i in 0..2 { let rx = rx.clone(); consumer_tasks.push(task::spawn(consumer(i, rx))); } assert_eq!(rx.receiver_count(), 3); for task in producer_tasks { let _ = task.await; } println!( "The current number of messages in the channel is {}", tx.len() ); // Close the channel to signal consumers that no more messages will be sent drop(tx); // or tx.close(); assert!(rx.is_closed()); for task in consumer_tasks { let _ = task.await; } // The channel is empty. assert!(rx.is_empty()); }
Broadcast messages from multiple producers to multiple consumers
postage
is a feature-rich, portable async channel library, with different options than Tokio. postage::broadcast
provides a lossless MPMC channel, which all receivers are guaranteed to receive each message.
use postage::broadcast; use postage::prelude::Stream; use postage::sink::Sink; use tokio::task; use tokio::time::Duration; async fn broadcaster(id: usize, mut tx: broadcast::Sender<String>) { for i in 0..2 { let msg = format!("Broadcaster {}'s message {}", id, i); if let Err(err) = tx.send(msg.clone()).await { // `send` returns Err(SendError(value)) // if the sink rejected the message. eprintln!("Failed to send message: {}", err); break; } println!("Sent: {}", msg); // Simulate work tokio::time::sleep(Duration::from_millis(10)).await; } } async fn receiver(name: &'static str, mut rx: broadcast::Receiver<String>) { while let Some(msg) = rx.recv().await { println!("{} receive {}", name, msg); } } #[tokio::main] async fn main() { // The broadcast channel provides reliable broadcast delivery between // multiple senders and multiple receivers. The channel has a fixed // capacity, and senders are suspended if the buffer is filled. let (tx, rx) = broadcast::channel(10); let mut broadcaster_tasks = vec![]; for i in 0..2 { let tx = tx.clone(); broadcaster_tasks.push(task::spawn(broadcaster(i, tx))); } // Let's create a couple of receivers: let rx2 = rx.clone(); task::spawn(receiver("A", rx)); task::spawn(receiver("B", rx2)); tokio::time::sleep(Duration::from_millis(50)).await; // We may also subscribe to the channel. // The receiver will observe all messages sent _after the call to // subscribe_. Messages currently in the buffer will not be received. let rx3 = tx.subscribe(); task::spawn(receiver("C", rx3)); let mut tx2 = tx.clone(); tx2.send("Last message".into()).await.ok(); // Wait for all the receivers to print tokio::time::sleep(Duration::from_millis(25)).await; }
kanal
Fast sync and async channel:
fn main() { todo!(); }
Streams
Recipe | Crates | Categories |
---|---|---|
Streams |
Futures are about a single value that will eventually be produced, but many event sources naturally produce a futures::stream::Stream
of values over time.
use futures::Stream; use futures::stream; use futures::stream::StreamExt; async fn count_to_five() -> impl Stream<Item = u32> { stream::iter(1..=5) } #[tokio::main] async fn main() { let mut stream = count_to_five().await; // `for` loops are not usable with Streams, but for imperative-style // code, `while let` and the `next`/`try_next` functions can be used: while let Some(num) = stream.next().await { println!("{}", num); } }
There are combinator-style methods such as futures::prelude::stream::StreamExt::map
⮳, futures::prelude::stream::StreamExt::filter
⮳, and futures::prelude::stream::StreamExt::fold
⮳, and their early-exit-on-error cousins futures::prelude::stream::TryStreamExt::try_filter
⮳, and futures::prelude::stream::TryStreamExt::try_fold
⮳.
To process multiple items from a stream concurrently, use the futures::prelude::stream::StreamExt::for_each_concurrent
⮳ and futures::prelude::stream::TryStreamExt::try_for_each_concurrent
⮳ methods:
use std::fs;
use futures::StreamExt;
use tokio::fs::File;
use tokio::io;
type Result = std::result::Result<(), anyhow::Error>;
async fn download_file(url: &str, filename: &str) -> Result {
let response = reqwest::get(url).await?;
let content = response.bytes().await?;
let mut file = File::create(filename).await?;
io::copy(&mut content.as_ref(), &mut file).await?;
Ok(())
}
#[tokio::main]
async fn main() -> Result {
let urls = ["https://www.gutenberg.org/cache/epub/43/pg43.txt"]; // add more here...
let filenames = ["temp/file1.txt"]; // add more here...
if !fs::exists("temp")? {
fs::create_dir("temp")?;
}
let futures = urls
.iter()
.zip(filenames.iter())
.map(|(url, filename)| download_file(url, filename));
let fut = futures::stream::iter(futures).for_each_concurrent(
4,
|fut| async move {
match fut.await {
Err(e) => {
println!("Error: {}", e);
match e.source() {
Some(source) => {
println!(" Caused by: {}", source);
}
_ => {}
}
}
_ => {}
}
},
);
fut.await;
println!("Downloaded files successfully!");
Ok(())
}
See also
Futures crate
Recipe | Crates | Categories |
---|---|---|
Selecting futures | ||
Joining futures | ||
Map, then, either, flatten |
The futures
⮳ crate provides a number of core abstractions for writing asynchronous code.
In most cases, you will use this crate directly only when writing async code intended to work for multiple runtimes. Otherwise, use the utilities provided by the ecosystem of your choice - Tokio for example.
Selecting futures
futures::future::Select
⮳ polls multiple futures and streams simultaneously, executing the branch for the future that finishes first. If multiple futures are ready, one will be pseudo-randomly selected at runtime.
use futures::{ future::FutureExt, // for `.fuse()` pin_mut, select, }; async fn task_one() { // ... } async fn task_two() { // ... } async fn race_tasks() { let t1 = task_one().fuse(); let t2 = task_two().fuse(); pin_mut!(t1, t2); select! { () = t1 => println!("task one completed first"), () = t2 => println!("task two completed first"), } } #[tokio::main] async fn main() { race_tasks().await; }
Joining futures
use futures::join; async fn foo(i: u32) -> u32 { println!("{i}"); i } #[tokio::main] async fn main() { // The `join!` macro polls multiple futures simultaneously, returning // a tuple of all results once complete. assert_eq!(join!(foo(1), foo(2)), (1, 2)); // `join!` is variadic, so you can pass any number of futures // `join_all` create a future which represents a collection of the // outputs of the futures given. let futures = vec![foo(1), foo(2), foo(3)]; assert_eq!(futures::future::join_all(futures).await, [1, 2, 3]); }
Map, then, either, flatten
The futures
⮳ crate provides an extension trait that provides a variety of convenient adapters.
use anyhow::Result; use futures::future::FutureExt; #[tokio::main] async fn main() -> Result<()> { let future_of_1 = async { 1 }; // Map this future’s output to a (possibly) different type, returning // a new future of the resulting type. let new_future = future_of_1.map(|x| x + 3); // Chain on a computation for when a future finished, passing the // result of the future to the provided closure f. let future_of_7 = new_future.then(|x| async move { x + 3 }); let seven = future_of_7.await; println!("{}", seven); assert_eq!(seven, 7); // Conditional `Either` future let x = 6; let future = if x > 10 { async { true }.left_future() } else { async { false }.right_future() }; let not_true: bool = future.await; assert!(!not_true); // Flatten nested futures let nested_future = async { async { 1 } }; let future = nested_future.flatten(); let flat = future.await; println!("{flat}"); assert_eq!(flat, 1); Ok(()) }
See also
futures = Utility functions for working with Futures and Streams
Mixing Async and Blocking Code
Call blocking code from async code
- Async code should never spend a long time without reaching an
.await
. - Don't carelessly mix async code and synchronous, blocking calls like
std::thread::sleep(Duration::from_secs(N));
- If you have to block the thread because of expensive CPU-bound computation, call to a synchronous IO API, use the
tokio::task::spawn_blocking
⮳ function, userayon
⮳, or spawn a dedicated thread.
See Async: What is blocking? blog post⮳.
Use spawn_blocking
Use tokio::task::spawn_blocking
⮳ to run a small portion of synchronous code.
#[tokio::main] async fn main() { // This is running on Tokio. We may not block here. let blocking_task = tokio::task::spawn_blocking(|| { // This is running on a thread where blocking is fine. println!("Inside spawn_blocking"); }); blocking_task.await.unwrap(); }
Use the rayon
crate
use rayon::prelude::*; async fn parallel_sum(nums: Vec<i32>) -> i32 { let (tx, rx) = tokio::sync::oneshot::channel(); // Spawn a task on rayon. rayon::spawn(move || { // Perform an expensive computation on this thread... // ...or compute the sum on multiple rayon threads. let sum = nums.par_iter().sum(); // Send the result back to Tokio. let _ = tx.send(sum); }); // Wait for the rayon task. rx.await.expect("Panic in rayon::spawn") } #[tokio::main] async fn main() { let nums = vec![1; 1024 * 1024]; println!("{}", parallel_sum(nums).await); }
Spawn a dedicated thread
If a blocking operation keeps running forever, you should run it on a dedicated thread.
async fn parallel_sum(nums: Vec<i32>) -> i32 { let (tx, rx) = tokio::sync::oneshot::channel(); // Spawn a task on a dedicate thread. std::thread::spawn(move || { // Perform an expensive computation on this thread... let sum = nums.into_iter().sum(); // Send the result back to Tokio. let _ = tx.send(sum); }); // Wait for the rayon task. rx.await.expect("Panic in rayon::spawn") } #[tokio::main] async fn main() { let nums = vec![1; 1024 * 1024]; println!("{}", parallel_sum(nums).await); }
Call async code from blocking code
In other cases, it may be easier to structure the application as largely synchronous, with smaller or logically distinct asynchronous portions. For instance, a GUI application might want to run the GUI code on the main thread and run a Tokio runtime next to it on another thread.
Use the futures executor
futures_executor
⮳ includes a minimal executor. The futures_executor::block_on
⮳ function is useful if you want to run an async function synchronously in codebase that is mostly synchronous.
async fn do_something() { println!("hello, world!"); } fn main() { let future = do_something(); // Futures are lazy - nothing is happening // until driven to completion by .await, block_on... // `block_on` blocks the current thread until the provided future has // run to completion. Other executors provide more complex // behavior, like scheduling multiple futures onto the same // thread. See `Tokio`. futures::executor::block_on(future); // `future` is run and "hello, world!" is printed }
Use the Tokio runtime directly
fn main() { let runtime = tokio::runtime::Builder::new_multi_thread() .worker_threads(1) .enable_all() .build() .unwrap(); let mut handles = Vec::with_capacity(10); for i in 0..10 { handles.push(runtime.spawn(my_bg_task(i))); } // Do something time-consuming while the async background tasks // execute. std::thread::sleep(std::time::Duration::from_millis(750)); println!("Finished time-consuming task."); // Wait for all of them to complete. for handle in handles { // The `spawn` method returns a `JoinHandle`. A `JoinHandle` is // a future, so we can wait for it using `block_on`. runtime.block_on(handle).unwrap(); } } // Example async code to execute async fn my_bg_task(i: u64) { // By subtracting, the tasks with larger values of i sleep for a // shorter duration. let millis = 1000 - 50 * i; println!("Task {} sleeping for {} ms.", i, millis); tokio::time::sleep(tokio::time::Duration::from_millis(millis)).await; println!("Task {} stopping.", i); }
Authentication
The process of confirming identities.
Basic Authentication
Recipe | Crates | Categories |
---|---|---|
Perform a basic HTTP authentication |
The Copenhagen Book provides a general guideline on implementing auth in web applications. The Copenhagen Book
Basic Authentication
Recipe | Crates | Categories |
---|---|---|
Perform a basic HTTP authentication |
Perform a basic HTTP authentication
Uses reqwest::RequestBuilder::basic_auth
to perform a basic HTTP authentication.
use std::fs::File;
use std::io::copy;
use anyhow::Result;
use tempfile::Builder;
#[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?;
let mut dest = {
let fname = response
.url()
.path_segments()
.and_then(|segments| segments.last())
.and_then(|name| if name.is_empty() { None } else { Some(name) })
.unwrap_or("tmp.bin");
println!("file to download: '{}'", fname);
let fname = tmp_dir.path().join(fname);
println!("will be located under: '{:?}'", fname);
File::create(fname)?
};
let content = response.text().await?;
copy(&mut content.as_bytes(), &mut dest)?;
Ok(())
}
Command Line
Techniques to help create command line interfaces, such as argument parsers, line editing, or output coloring and formatting
Recipe | Crates | Categories |
---|---|---|
Using clap's builder API | ||
Using clap 's derive API | ||
lexopt | ||
pico-args |
| structopt
| | |
| ansiterm
| | |
| anstream
| | |
| anstyle
| | |
| console
| | |
| owo-colors
| | |
| stylish
| | |
| termcolor
| | |
| yansi
| | |
| termion
| | |
Recipe | Crates | Categories |
---|---|---|
Build complex TUI |
Recipe | Crates | Categories |
---|---|---|
Ask for confirmation, selection, text input | ||
Display progress bars and spinners |
See also
Command Line Applications in Rust (book)⮳
Code⮳ for Command-Line Rust
(O'Reilly, 2022, ISBN 9781098109417)
Command-line argument parsing
Recipe | Crates | Categories |
---|---|---|
Using clap's builder API | ||
Using clap 's derive API | ||
lexopt | ||
pico-args |
| structopt
| | |
Using clap's builder API
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 clap::Arg::short
⮳ and clap::Arg::long
⮳ options control the flag the user will be expected to type; short flags look like -f
and long flags look like --file
.
use std::path::PathBuf; use clap::Arg; use clap::Command; use clap::value_parser; fn cli() -> Command { clap::Command::new("My Test Program") .bin_name("test_app") .version("0.1.0") .author("Hackerman Jones <hckrmnjones@hack.gov>") .about("Teaches argument parsing") // First possible argument: --num or -n .arg(Arg::new("num") .short('n') // -n argument .long("number") // --number long-form argument .value_name("NUMBER") // placeholder for the argument's value in the help message / usage. .required(false) .help("Enter your favorite number")) // Second possible argument: --file or -f .arg(Arg::new("file") .short('f') .long("file") .value_parser(value_parser!(PathBuf)) .help("Enter the path of a file")) // You can also use the arg! macro: .arg(clap::arg!(-c --config <CONFIG> // "Optionally sets a config file to use")) } fn main() { let matches = cli().get_matches_from(["test_app", "-n", "42", "--file", "README.md"]); // In a real program, use the following to retrieve arguments from the // command line: let matches = cli().get_matches(); if let Some(num) = matches.get_one::<String>("num") { println!("Value for num: {num}"); } if let Some(file_path) = matches.get_one::<PathBuf>("file") { println!("Value for file: {}", file_path.display()); } }
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
We can test the application by running a command like the following.
cargo run -- -f myfile.txt -n 251
The output is:
The file passed is: myfile.txt
Your favorite number must be 256.
Using clap
's derive API
use std::path::PathBuf; use anyhow::Result; use clap::Parser; use clap::Subcommand; // The struct declaring the desired command-line arguments and // commands // The `derive` feature flag is required (see Cargo.toml). #[derive(Parser, Debug)] // Reads the following attributes the from the package's `Cargo.toml` // Alternatively, use #[command(name = "MyApp")] ... #[command(author, version, about, long_about = None)] // Displays Help if no arguments are provided #[command(arg_required_else_help = true)] pub struct Cli { // Positional argument example /// The pattern to look for (the doc comment appears in the help) pattern: Option<String>, /// Required argument example (with default value and validation) #[arg(default_value_t = 8080)] #[arg(value_parser = clap::value_parser!(u16).range(1..))] port: u16, // Named argument example: the path to the file to look into #[arg(short, long)] path: Option<PathBuf>, /// Count example: turn debugging information on #[arg(short, long, action = clap::ArgAction::Count)] debug: u8, // // Alternatively, use the // // `clap-verbosity-flag` crate: // // It adds the following flags through the entire program: // // -q silences output // // -v show warnings // // -vv show info // // -vvv show debug // // -vvvv show trace // // By default, this will only report errors. // #[clap(flatten)] // verbose: clap_verbosity_flag::Verbosity, // Subcommands #[command(subcommand)] pub command: Option<Commands>, } // The subcommands #[derive(Subcommand, Debug)] pub enum Commands { /// Read something #[command(arg_required_else_help = true)] Read { /// A boolean flag #[arg(short, long)] all: bool, #[arg(required = true)] file: Vec<PathBuf>, }, /// Say something Tell, } fn main() -> Result<()> { // `Clap` returns a Cli struct populated from `std::env::args_os()`... let cli = Cli::try_parse()?; // You also could use `parse()` // The argument values we got... println!("Path: {:?}", cli.path); // Use `unwrap_or` to set defaults for optional arguments // (or use `default_value_t` as above). println!("Pattern: {:?}", cli.pattern.unwrap_or("".to_string())); // You can see how many times a particular flag or argument occurred // Note, only flags can have multiple occurrences match cli.debug { 0 => println!("Debug mode is off"), 1 => println!("Debug mode is kind of on"), 2 => println!("Debug mode is on"), _ => println!("Don't be crazy"), } // // Alternatively, use the `verbose` flag, if configured above // env_logger::Builder::new() // .filter_level(cli.verbose.log_level_filter()) // .init(); // Check for the existence of subcommands match &cli.command { Some(Commands::Read { all, file }) => { if *all { println!("Read all..."); } else { println!("Read just one..."); } println!("{:?}", file); } Some(Commands::Tell) => { println!("{}", 42); } None => {} } Ok(()) }
See also
lexopt
Fast compile times, fast runtime, pedantic about correctness. API is less ergonomic
fn main() { todo!(); }
pico-args
Fast compile times, fast runtime, more lax about correctness. API is more ergonomic
fn main() { todo!(); }
structopt
Parse command line argument by defining a struct.
Argument parsing
ANSI Terminal
| ansiterm
| | |
| anstream
| | |
| anstyle
| | |
| console
| | |
| owo-colors
| | |
| stylish
| | |
| termcolor
| | |
| yansi
| | |
| termion
| | |
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
⮳: ansi_term::ANSIString
⮳ and Style
⮳. A Style
holds stylistic information: colors, whether the text should be bold, or blinking, or whatever. There are also Colour variants that represent simple foreground colour styles. An ansi_term::ANSIString
⮳ is a string paired with a ansi_term::Style
⮳.
Note: British English uses Colour instead of Color.
Print colored text to the terminal
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") ); }
Print bold text to the terminal
For anything more complex than plain foreground color changes, the code needs to construct ansi_term::Style
⮳ struct. ansi_term::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") ); }
Print bold and colored text to the terminal
ansi_term::Color
⮳ implements many similar functions as ansi_term::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") ); }
Manipulate the cursor, style the output, handle input events
Low-level cross-platform terminal rendering and event handling.
Crossterm is a pure-rust, terminal manipulation library that makes it possible to write cross-platform text-based interfaces. It supports all UNIX and Windows terminals down to Windows 7
- Full control over writing and flushing output buffer
- Is tty
- Cursor manipulation
- Styled output
- Terminal handling
- Events (key inputs, mouse...)
Most popular
termcolor
A simple cross platform library for writing colored text to a terminal
anstyle
ANSI text styling
anstream
A simple cross platform library for writing colored text to a terminal.
Library for ANSI terminal colors and styles (bold, underline)
ansiterm
Library for ANSI terminal colours and styles (bold, underline)
console
A terminal and console abstraction for Rust
owo-colors
Zero-allocation terminal colors that will make people go owo
stylish
Yet another crate implementing colorized text
yansi
A dead simple ANSI terminal color painting library.
termion
Termion is a pure Rust, bindless library for low-level handling, manipulating and reading information about terminals. This provides a full-featured alternative to Termbox.
Termion aims to be simple and yet expressive. It is bindless, meaning that it is not a front-end to some other library (e.g., ncurses or termbox), but a standalone library directly talking to the TTY.
Terminal User Interfaces
Recipe | Crates | Categories |
---|---|---|
Build complex TUI |
Build complex TUI
ratatui⮳ is a lightweight, high-level library that provides a set of widgets, layouts, and utilities to build complex Rust TUIs.
fn main() { todo!(); }
See also
A library to build rich terminal user interfaces or dashboards
User interaction
Recipe | Crates | Categories |
---|---|---|
Ask for confirmation, selection, text input | ||
Display progress bars and spinners |
Ask for confirmation, selection, text input
inquire
provides several different prompts in order to interactively ask the user for information via the CLI.
use inquire::Confirm; use inquire::Select; use inquire::Text; // The `inquire`` crate is used for creating interactive CLI prompts. fn main() { let ans = Confirm::new("Do you want to proceed?") .with_default(false) .prompt() .expect("Failed to read input"); println!("You answered: {:?}", ans); // Prompt for the user's name let name = Text::new("What's your name?") .prompt() .expect("Failed to read input"); // Prompt for the user's favorite programming language let languages = vec!["Rust", "Python", "JavaScript", "C++", "Go", "Other"]; let favorite_language = Select::new("What's your favorite programming language?", languages) //.with_help_message("Hint: it is Rust!") .prompt() .expect("Failed to read input"); // Display the collected information println!("Hello, {}!", name); println!( "Your favorite programming language is {}.", favorite_language ); }
Display progress bars and spinners
indicatif
⮳ is a Rust library for indicating progress in command line applications to users.
This currently primarily provides progress bars and spinners as well as basic color support.
use std::thread;
use std::time::Duration;
use console::Emoji;
use console::Style;
use console::Term;
use indicatif::ProgressBar;
use indicatif::ProgressStyle;
fn spinner() -> anyhow::Result<()> {
// `console` is a library for Rust that provides access to
// terminal features, like styles and colors.
// The terminal is abstracted through the `console::Term` type.
let term = Term::stdout();
term.clear_screen()?;
term.write_line("Simulate the output of a compiler:")?;
thread::sleep(Duration::from_millis(20));
term.clear_line()?;
// Print with styles / colors
println!(
"Target: {}",
console::style("x86_64-unknown-linux-gnu").cyan()
);
// `indicatif` offers progress bars and spinners.
// Create a progress bar with an indeterminate length:
let pb = ProgressBar::new_spinner();
pb.set_style(
ProgressStyle::default_spinner()
.template("{spinner:.green} [{elapsed_precise}] {msg}")
.expect("Failed to set progress style"),
);
// Simulate different stages of the compilation process
let stages = vec![
("Parsing source files", 1),
("Type checking", 3),
("Optimizing code", 2),
("Generating bytecode", 1),
("Linking objects", 1),
("Finalizing build", 1),
];
// Spawns a background thread to tick the progress bar
pb.enable_steady_tick(Duration::from_millis(100));
for (stage, duration) in stages {
pb.set_message(stage);
thread::sleep(Duration::from_secs(duration));
}
pb.disable_steady_tick();
pb.finish_with_message("Compilation complete!");
// Print with style
let cyan = Style::new().cyan();
println!("Executable: {}", cyan.apply_to("./target/debug/my_program"));
// Use emojis
println!("{} Done!", Emoji("✨", ":-)"));
Ok(())
}
fn main() -> anyhow::Result<()> {
spinner()?;
Ok(())
}
use std::thread;
use std::time::Duration;
use indicatif::ProgressBar;
use indicatif::ProgressStyle;
fn progress() {
// Create a new progress bar with a length of 100
// This progress bar by default draws directly to stderr.
// If a non terminal is detected,
// the progress bar will be completely hidden.
let pb = ProgressBar::new(100);
// Set a custom style for the progress bar
pb.set_style(ProgressStyle::default_bar()
// 40 characters wide and has cyan as primary style color and blue as alternative style color.
.template("{spinner:.green} [{elapsed_precise}] {bar:40.cyan/blue} {pos:>7}/{len:7} {msg}")
.unwrap()
.progress_chars("=>."));
for _ in 0..100 {
// Update the progress bar
pb.inc(1);
// Simulate work by sleeping for a short duration
thread::sleep(Duration::from_millis(50));
}
// Finish the progress bar
pb.finish_with_message("done"); // or use .finish();
// Alternatively, you could wrap an iterator with a progress bar:
// use indicatif::ProgressIterator;
// for _ in (0..100).progress() {
// thread::sleep(Duration::from_millis(50));
// }
}
fn main() -> anyhow::Result<()> {
progress();
Ok(())
}
Command-line utilities written in Rust
Filesystem
Networking
Recipe | Crates | Categories |
---|---|---|
gping |
Shells
See also
My terminal became more Rusty⮳
File listing and display
lsd
lsd
is a rewrite of GNU ls with lots of added features like colors, icons, tree-view, additional formatting options.
apt install lsd
exa
exa is a modern replacement for ls.
broot
broot
is a new way to see and navigate directory trees.
bat
bat
is a fast cat
clone with syntax highlighting and Git integration.
bat README.md
# Display multiple files at once
bat src/*.rs
# Read from stdin, determine the syntax automatically
curl -s https://sh.rustup.rs | bat
# Show and highlight non-printable characters:
bat -A /etc/hosts
open
Opens a path or URL using the program configured on the system.
Networking
Recipe | Crates | Categories |
---|---|---|
gping |
gping
Ping, but with a graph gping
apt install gping
Shells and related utilities
starship
is a fast, highly customizable prompt for any shell.
nushell
Compression
Algorithms for making data smaller.
tar
Recipe | Crates | Categories |
---|---|---|
Compress a directory into a tarball | ||
Decompress a tarball | ||
Decompress a tarball while removing a prefix from the paths |
Working with tarballs
Recipe | Crates | Categories |
---|---|---|
Compress a directory into a tarball | ||
Decompress a tarball | ||
Decompress a tarball while removing a prefix from the paths |
flate2
uses a pure-Rust implementation by default. Use feature flags to opt in to system zlib
.
Decompress a tarball
Decompress (flate2::read::GzDecoder
⮳) and extract (tar::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;
pub fn main() -> Result<(), std::io::Error> {
let path = "temp/archive.tar.gz";
let tar_gz = File::open(path)?;
let tar = GzDecoder::new(tar_gz);
let mut archive = Archive::new(tar);
archive.unpack(".")?;
println!("archive file unpacked!");
Ok(())
}
Compress a directory into a tarball
Compress /var/log
directory into archive.tar.gz
.
Creates a std::fs::File
⮳ wrapped in flate2::write::GzEncoder
⮳ and tar::Builder
⮳
Adds contents of /var/log
directory recursively into the archive under backup/logs
path with tar::Builder::append_dir_all
⮳. flate2::write::GzEncoder
⮳ is responsible for transparently compressing the data prior to writing it into archive.tar.gz
.
use std::fs;
use std::fs::File;
use flate2::Compression;
use flate2::write::GzEncoder;
pub fn main() -> Result<(), std::io::Error> {
// Create a temporary folder
if !fs::exists("temp")? {
fs::create_dir("temp")?;
}
// Create the archive file
let tar_gz_file = File::create("temp/archive.tar.gz")?;
// Create the compression encoder
let enc = GzEncoder::new(tar_gz_file, Compression::default());
// Create a new archive builder with the underlying file as the
// destination of all data written.
let mut tar = tar::Builder::new(enc);
// Archive the /var/log directory and all of its contents
// (recursively), renaming it in the process
tar.append_dir_all("temp/backup/var/log", "/var/log")?;
println!("`archive.tar.gz` file created!");
Ok(())
}
Decompress a tarball while removing a prefix from the paths
Iterate over the tar::Archive::entries
⮳. Use std::path::Path::strip_prefix
⮳ to remove the specified path prefix (bundle/logs
). Finally, extract the tar::Entry
⮳ via tar::Entry::unpack
⮳.
use std::fs::File;
use std::path::PathBuf;
use anyhow::Result;
use flate2::read::GzDecoder;
use tar::Archive;
pub fn main() -> Result<()> {
let file = File::open("temp/archive.tar.gz")?;
let mut archive = Archive::new(GzDecoder::new(file));
let prefix = "bundle/logs";
println!("Extracted the following files:");
archive
.entries()?
.filter_map(|e| e.ok())
.map(|mut entry| -> Result<PathBuf> {
let path = entry.path()?.strip_prefix(prefix)?.to_owned();
entry.unpack(&path)?;
Ok(path)
})
.filter_map(|e| e.ok())
.for_each(|x| println!("> {}", x.display()));
Ok(())
}
Concurrency
This section covers concurrent programming, and specifically parallel programming.
Parallelism implies:
- True simultaneous execution of multiple tasks on multiple cores or processors.
- Mechanism: uses operating system threads.
- Important for CPU-heavy computations.
- Often requires explicit management of threads and thread pools.
- Requires careful synchronization to prevent data races (using mechanisms like mutexes or atomics).
- Overhead due to thread creation and switching.
Key constructs in Rust:
- Threads are independent units of execution that can be spawned using e.g.
std::thread::spawn
. - Mutexes e.g.
std::sync::Mutex
protect shared data from race conditions. - Channels e.g.
std::sync::mpsc
allow threads to communicate and exchange data.
Explicit threads
Recipe | Crates | Categories |
---|---|---|
Use spawn, join | [] | |
Use scoped threads |
Threadpools
Recipe | Crates | Categories |
---|---|---|
Calculate the SHA256 of ISO files concurrently | ||
Draw a fractal, dispatching work to a thread pool |
Multithreading with the crossbeam
crate
Recipe | Crates | Categories |
---|---|---|
Spawn a short-lived thread | ||
Create a parallel pipeline | ||
Pass data between two threads |
Message passing and channels
Recipe | Crates | Categories |
---|---|---|
Multiple producers, single consumer | ||
crossbeam-channel | ||
flume | ||
tokio |
Shared state
Recipe | Crates | Categories |
---|---|---|
Maintain a global mutable state | ||
Mutexes | ||
parking_lot | ||
Atomics | ||
arc-swap |
Concurrent data structures
Recipe | Crates | Categories |
---|---|---|
Bounded multi-producer multi-consumer queue | ||
dashmap | ||
flurry |
Data parallelism
Send
and Sync
Recipe | Crates | Categories |
---|---|---|
Send and Sync traits |
See also
Explicit threads
Recipe | Crates | Categories |
---|---|---|
Use spawn, join | [] | |
Use scoped threads |
Use spawn & join
use std::thread; use std::time::Duration; fn main() { let thread_one = thread::spawn(|| { for i in 1..10 { println!("hi number {} from the spawned thread!", i); thread::sleep(Duration::from_millis(1)); } }); let thread_two = thread::spawn(|| { /* ... */ }); // More stufff // Wait for both threads to complete. thread_one.join().expect("thread one panicked"); thread_two.join().expect("thread two panicked"); }
Note: when the main thread of a Rust program completes, all spawned threads are shut down, whether or not they have finished running.
Use scoped threads
use std::error::Error; use std::path::Path; use std::sync::mpsc; use std::thread; // Our error type needs to be `Send` to be used in a channel fn read_contents<T: AsRef<Path>>( file: T, ) -> Result<String, Box<dyn Error + Send>> { Ok(file.as_ref().to_string_lossy().into_owned()) } fn main() { // To share state between threads, consider using a channel let (tx, rx) = mpsc::channel(); thread::scope(|scope| { // Creates a “fork-join” scope let tx2 = tx.clone(); scope.spawn(move || { println!("hello from the first scoped thread"); let contents = read_contents("foo.txt"); tx.send(contents).unwrap(); }); scope.spawn(move || { println!("hello from the second scoped thread"); let contents = read_contents("bar.txt"); tx2.send(contents).unwrap(); }); }); // No join. // Spawned threads get joined automatically once the scope ends! // Receive messages from the channel println!("hello from the main thread"); for received in rx { println!("Got: {:?}", received); } }
Threadpools
Recipe | Crates | Categories |
---|---|---|
Calculate the SHA256 of ISO files concurrently | ||
Draw a fractal, dispatching work to a thread pool |
Calculate the SHA256 of ISO files concurrently
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::WalkDir::new
⮳ iterates the current directory and calls walkdir::WalkDir::new
⮳ to perform the operations of reading and computing SHA256 hash.
use std::fs::File; use std::io::BufReader; use std::io::Error; use std::io::Read; use std::path::Path; use std::sync::mpsc::channel; use ring::digest::Context; use ring::digest::Digest; use ring::digest::SHA256; use threadpool::ThreadPool; use walkdir::WalkDir; // Verify the iso extension fn is_iso(entry: &Path) -> bool { matches!(entry.extension(), Some(e) if e.to_string_lossy().to_lowercase() == "iso") } fn compute_digest<P: AsRef<Path>>(filepath: P) -> Result<(Digest, P), Error> { let mut buf_reader = BufReader::new(File::open(&filepath)?); let mut context = Context::new(&SHA256); let mut buffer = [0; 1024]; loop { let count = buf_reader.read(&mut buffer)?; if count == 0 { break; } context.update(&buffer[..count]); } Ok((context.finish(), filepath)) } fn main() -> Result<(), Error> { let pool = ThreadPool::new(num_cpus::get()); let (tx, rx) = channel(); for entry in WalkDir::new("/home/user/Downloads") .follow_links(true) .into_iter() .filter_map(|e| e.ok()) .filter(|e| !e.path().is_dir() && is_iso(e.path())) { let path = entry.path().to_owned(); let tx = tx.clone(); pool.execute(move || { let digest = compute_digest(path); tx.send(digest).expect("Could not send data!"); }); } drop(tx); for t in rx.iter() { let (sha, path) = t?; println!("{:?} {:?}", sha, path); } Ok(()) }
Draw a fractal, dispatching work to a thread pool
This example generates an image by drawing a fractal from the Julia set⮳ with a thread pool for distributed computation.
Allocate memory for output image of given width and height with image::ImageBuffer::new
⮳.
image::Rgb::from_channels
⮳ calculates RGB pixel values. Create threadpool::ThreadPool
⮳ with thread count equal to number of cores with num_cpus::get
⮳.
threadpool::ThreadPool::execute
⮳ receives each pixel as a separate job.
std::sync::mpsc::channel
⮳ receives the jobs and std::sync::mpsc::Receiver::recv
⮳ retrieves them.
image::ImageBuffer::put_pixel
⮳ uses the data to set the pixel color.
image::ImageBuffer::save
⮳ writes the image to output.png
.
use std::fs;
use std::sync::mpsc::channel;
use anyhow::Result;
use image::ImageBuffer;
use image::Rgb;
use num::complex::Complex;
use threadpool::ThreadPool;
// Function converting intensity values to RGB
fn wavelength_to_rgb(wavelength: u32) -> Rgb<u8> {
let wave = wavelength as f32;
let (r, g, b) = match wavelength {
380..=439 => ((440. - wave) / (440. - 380.), 0.0, 1.0),
440..=489 => (0.0, (wave - 440.) / (490. - 440.), 1.0),
490..=509 => (0.0, 1.0, (510. - wave) / (510. - 490.)),
510..=579 => ((wave - 510.) / (580. - 510.), 1.0, 0.0),
580..=644 => (1.0, (645. - wave) / (645. - 580.), 0.0),
645..=780 => (1.0, 0.0, 0.0),
_ => (0.0, 0.0, 0.0),
};
let factor = match wavelength {
380..=419 => 0.3 + 0.7 * (wave - 380.) / (420. - 380.),
701..=780 => 0.3 + 0.7 * (780. - wave) / (780. - 700.),
_ => 1.0,
};
let (r, g, b) = (
normalize(r, factor),
normalize(g, factor),
normalize(b, factor),
);
Rgb([r, g, b])
}
// Maps Julia set distance estimation to intensity values
fn julia(
c: Complex<f32>,
x: u32,
y: u32,
width: u32,
height: u32,
max_iter: u32,
) -> u32 {
let width = width as f32;
let height = height as f32;
let mut z = Complex {
// scale and translate the point to image coordinates
re: 3.0 * (x as f32 - 0.5 * width) / width,
im: 2.0 * (y as f32 - 0.5 * height) / height,
};
let mut i = 0;
for t in 0..max_iter {
if z.norm() >= 2.0 {
break;
}
z = z * z + c;
i = t;
}
i
}
// Normalizes color intensity values within RGB range
fn normalize(color: f32, factor: f32) -> u8 {
((color * factor).powf(0.8) * 255.) as u8
}
fn main() -> Result<()> {
let (width, height) = (1920, 1080);
let mut img = ImageBuffer::new(width, height);
let iterations = 300;
let c = Complex::new(-0.8, 0.156);
let pool = ThreadPool::new(num_cpus::get());
let (tx, rx) = channel();
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!");
}
});
}
for _ in 0..(width * height) {
let (x, y, pixel) = rx.recv()?;
img.put_pixel(x, y, pixel);
}
if !fs::exists("temp")? {
fs::create_dir("temp")?;
}
img.save("temp/output.png")?;
println!("Image saved!");
Ok(())
}
Multithreading with the crossbeam
crate
Recipe | Crates | Categories |
---|---|---|
Spawn a short-lived thread | ||
Create a parallel pipeline | ||
Pass data between two threads |
Spawn a short-lived thread
The example uses the crossbeam
⮳ crate, which provides data structures and functions for concurrent and parallel programming. crossbeam::thread::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)); println!("The maximum is {:?}", max); } fn find_max(arr: &[i32]) -> Option<i32> { const THRESHOLD: usize = 2; if arr.len() <= THRESHOLD { return arr.iter().cloned().max(); } let mid = arr.len() / 2; let (left, right) = arr.split_at(mid); crossbeam::scope(|s| { let thread_l = s.spawn(|_| find_max(left)); let thread_r = s.spawn(|_| find_max(right)); let max_l = thread_l.join().unwrap()?; let max_r = thread_r.join().unwrap()?; Some(max_l.max(max_r)) }) .unwrap() }
Create a parallel pipeline
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 std::ops::Drop
⮳ to prevent the entire program from blocking on the worker for-loops. You can think of the calls to std::ops::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); }); // Parallel processing by 2 threads for _ in 0..n_workers { // Send to sink, receive from source let (sendr, recvr) = (snd2.clone(), rcv1.clone()); // Spawn workers in separate threads s.spawn(move |_| { thread::sleep(Duration::from_millis(500)); // Receive until channel closes for msg in recvr.iter() { println!( "Worker {:?} received {}.", thread::current().id(), msg ); sendr.send(msg * 2).unwrap(); } }); } // Close the channel, otherwise sink will never // exit the for-loop drop(snd2); // Sink for msg in rcv2.iter() { println!("Sink received {}", msg); } }) .unwrap(); }
Pass data between two threads
This example demonstrates the use of crossbeam_channel
⮳ in a single producer, single consumer (SPSC) setting. We build off the crossbeam spawn
⮳ example by using crossbeam::scope
⮳ and crossbeam::thread::Scope::spawn
⮳ to manage the producer thread. Data is exchanged between the two threads using a crossbeam::scope
⮳ channel, meaning there is no limit to the number of storable messages. The producer thread sleeps for half a second in between messages.
use std::thread; use std::time; use crossbeam_channel::unbounded; 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); } }
Parallel Tasks
rayon
Simple work-stealing parallelism for Rust.
Iterate in parallel
Convert .iter()
or iter_mut()
or into_iter()
into par_iter()
or par_iter_mut()
or into_par_iter()
to execute in parallel.
use rayon::prelude::*; fn sum_of_squares(input: &[i32]) -> i32 { input.par_iter().map(|i| i * i).sum() } fn increment_all(input: &mut [i32]) { input.par_iter_mut().for_each(|p| *p += 1); } fn main() { let mut v = [1, 2, 3]; increment_all(&mut v[..]); println!("{}", sum_of_squares(&v[..])); }
Sort in parallel
use rayon::prelude::*; fn main() { let mut v = [-5, 4, 1, -3, 2]; v.par_sort(); println!("{:#?}", v); }
Implement custom parallel tasks
Rayon implements rayon::join
⮳, rayon::join
⮳, rayon::spawn
⮳ that may run on the global or a custom Rayon threadpool⮳.
fn main() { // Build the threadpool let pool = rayon::ThreadPoolBuilder::new() .num_threads(8) .build() .unwrap(); // `install` executes the closure within the threadpool. Any attempts // to use join, scope, or parallel iterators will then operate // within that threadpool. let n = pool.install(|| fib(20)); println!("{}", n); } fn fib(n: usize) -> usize { if n == 0 || n == 1 { return n; } // Conceptually, calling join() is similar to spawning two threads, // one executing each of the two closures. let (a, b) = rayon::join(|| fib(n - 1), || fib(n - 2)); // runs inside of `pool` a + b }
Mutate the elements of an array in parallel
The example uses the rayon
⮳ crate, which is a data parallelism library for Rust.
rayon
⮳ provides the rayon::iter::IntoParallelRefIterator::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); }
Test in parallel if any or all elements of a collection match a given predicate
This example demonstrates using the rayon::iter::ParallelIterator::any
⮳ and rayon::iter::ParallelIterator::any
⮳ methods, which are parallelized counterparts to std::iter::Iterator::any
⮳ and std::iter::Iterator::all
⮳. rayon::iter::ParallelIterator::any
⮳ checks in parallel whether any element of the iterator matches the predicate, and returns as soon as one is found. rayon::iter::ParallelIterator::any
⮳ 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]; assert!(!vec.par_iter().any(|n| (*n % 2) != 0)); assert!(vec.par_iter().all(|n| (*n % 2) == 0)); assert!(!vec.par_iter().any(|n| *n > 8)); assert!(vec.par_iter().all(|n| *n <= 8)); vec.push(9); assert!(vec.par_iter().any(|n| (*n % 2) != 0)); assert!(!vec.par_iter().all(|n| (*n % 2) == 0)); assert!(vec.par_iter().any(|n| *n > 8)); assert!(!vec.par_iter().all(|n| *n <= 8)); println!("{:?}", vec); }
Search items using a given predicate in parallel
This example uses rayon::iter::ParallelIterator::find_any
⮳ and rayon::iter::ParallelIterator::find_any
⮳ 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::iter::ParallelIterator::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::iter::Iterator::find
⮳ for additional details.
use rayon::prelude::*; fn main() { let v = vec![6, 2, 1, 9, 3, 8, 11]; let f1 = v.par_iter().find_any(|&&x| x == 9); let f2 = v.par_iter().find_any(|&&x| x % 2 == 0 && x > 6); let f3 = v.par_iter().find_any(|&&x| x > 8); assert_eq!(f1, Some(&9)); assert_eq!(f2, Some(&8)); assert!(f3 > Some(&8)); println!("{:?}", v); }
Sort a vector in parallel
This example will sort in parallel a vector of Strings.
Allocate a vector of empty Strings. par_iter_mut().for_each
populates random values in parallel. Although multiple options⮳
exist to sort an enumerable data type, rayon::slice::ParallelSliceMut::par_sort_unstable
⮳ is usually faster than stable sort ⮳ algorithms.
use rand::Rng; use rand::distributions::Alphanumeric; use rand::thread_rng; use rayon::prelude::*; fn main() { let mut vec = vec![String::new(); 100]; vec.par_iter_mut().for_each(|p| { let mut rng = thread_rng(); *p = (0..5).map(|_| rng.sample(Alphanumeric) as char).collect(); }); vec.par_sort_unstable(); println!("{:?}", vec); }
Map-reduce in parallel
This example uses rayon::iter::ParallelIterator::filter
⮳ rayon::iter::ParallelIterator::map
⮳ and rayon::iter::ParallelIterator::reduce
⮳ to calculate the average age of Person
objects whose age is over 30.
rayon::iter::ParallelIterator::filter
⮳ returns elements from a collection that satisfy the given predicate. rayon::iter::ParallelIterator::map
⮳ performs an operation on every element, creating a new iteration, and rayon::iter::ParallelIterator::reduce
⮳ performs an operation given the previous reduction and the current element. Also shows use of rayon::iter::ParallelIterator::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 }, ]; let num_over_30 = v.par_iter().filter(|&x| x.age > 30).count() as f32; let sum_over_30 = v .par_iter() .map(|x| x.age) .filter(|&x| x > 30) .reduce(|| 0, |x, y| x + y); let alt_sum_30: u32 = v.par_iter().map(|x| x.age).filter(|&x| x > 30).sum(); let avg_over_30 = sum_over_30 as f32 / num_over_30; let alt_avg_over_30 = alt_sum_30 as f32 / num_over_30; assert!((avg_over_30 - alt_avg_over_30).abs() < f32::EPSILON); println!("The average age of people older than 30 is {}", avg_over_30); }
Generate JPEG thumbnails in parallel
This example generates thumbnails for all jpg
files in the current directory then saves them in a new folder called thumbnails
.
glob::glob_with::glob_with
⮳ finds jpeg files in current directory. rayon
resizes images in parallel using rayon::iter::IntoParallelRefIterator::par_iter
⮳ calling image::DynamicImage::resize
⮳
use std::fs::create_dir_all;
use std::path::Path;
use anyhow::Context;
use anyhow::Result;
use glob::MatchOptions;
use glob::glob_with;
use image::imageops::FilterType;
use rayon::prelude::*;
fn main() -> Result<()> {
let options: MatchOptions = Default::default();
let files: Vec<_> = glob_with("*.jpg", options)?
.filter_map(|x| x.ok())
.collect();
if files.is_empty() {
println!("No .jpg files found in current directory");
return Ok(());
}
let thumb_dir = "thumbnails";
create_dir_all(thumb_dir)?;
println!("Saving {} thumbnails into '{}'...", files.len(), thumb_dir);
let image_failures: Vec<_> = files
.par_iter()
.map(|path| {
make_thumbnail(path, thumb_dir, 300)
.with_context(|| path.display().to_string())
})
.filter_map(|x| x.err())
.collect();
image_failures.iter().for_each(|x| println!("{}", x));
println!(
"{} thumbnails saved successfully",
files.len() - image_failures.len()
);
Ok(())
}
fn make_thumbnail<PA, PB>(
original: PA,
thumb_dir: PB,
longest_edge: u32,
) -> Result<()>
where
PA: AsRef<Path>,
PB: AsRef<Path>,
{
let img = image::open(original.as_ref())?;
let file_path = thumb_dir.as_ref().join(original);
Ok(img
.resize(longest_edge, longest_edge, FilterType::Nearest)
.save(file_path)?)
}
Message passing and channels
Recipe | Crates | Categories |
---|---|---|
Multiple producers, single consumer | ||
crossbeam-channel | ||
flume | ||
tokio |
One increasingly popular approach to ensuring safe concurrency is message passing, where threads communicate by sending each other messages containing data. The Rust standard library provides channels for message passing that are safe to use in concurrent contexts.
Message passing in async
⮳ programming is covered in a separate page: async channels.
Multiple producers, single consumer
use std::sync::mpsc; use std::thread; use std::time::Duration; fn main() { let (tx, rx) = mpsc::channel(); let tx2 = tx.clone(); thread::spawn(move || { let vals = vec![String::from("hi"), String::from("hi again")]; for val in vals { tx.send(val).unwrap(); thread::sleep(Duration::from_secs(1)); } }); thread::spawn(move || { let vals = vec![String::from("more"), String::from("messages")]; for val in vals { tx2.send(val).unwrap(); thread::sleep(Duration::from_secs(1)); } }); while let Ok(msg) = rx.recv() { println!("{msg}"); } }
crossbeam-channel
Multi-producer multi-consumer channels for message passing. The absolute fastest channel implementation available. Implements Go-like 'select' feature.
use std::thread; use crossbeam_channel::RecvError; use crossbeam_channel::TryRecvError; use crossbeam_channel::unbounded; fn main() { // Create a channel of unbounded capacity. let (s1, r1) = unbounded(); // Alternatively, create a channel that can hold at most n messages at // a time. let (s1, r1) = bounded(5); // Senders and receivers can be cloned to use them to multiple // threads. cloning only creates a new handle to the same sending // or receiving side. It does not create a separate stream of // messages in any way let s2 = s1.clone(); // Send a message into the channel. // Note that the cloned sender is moved into the thread. thread::spawn(move || s2.send("Hi!").unwrap()); // Blocks until receiving the message from the channel. assert_eq!(r1.recv(), Ok("Hi!")); // Try receiving a message without blocking. // The channel is now empty assert_eq!(r1.try_recv(), Err(TryRecvError::Empty)); s1.send("0").unwrap(); // Receive all remaining messages currently in the channel // (non-blocking). let v: Vec<_> = r1.try_iter().collect(); println!("{:?}", v); // When all senders or all receivers associated with a channel get // dropped, the channel becomes disconnected. s1.send("1").unwrap(); drop(s1); // No more messages can be sent... // ERROR s1.send("2").unwrap(); // .. but any remaining messages can still be received. println!("{:?}", r1.iter().collect::<Vec<_>>()); // Note that the call to `collect` would block if the channel were not // disconnected. // There are no more messages in the channel. assert!(r1.is_empty()); // After disconnection, calling `r1.recv()` does not block // Instead, `Err(RecvError)` is returned immediately. assert_eq!(r1.recv(), Err(RecvError)); }
Example using specialized channels for tickers
and timeout
use std::time::Duration; use std::time::Instant; use crossbeam_channel::after; use crossbeam_channel::select; use crossbeam_channel::tick; fn main() { let start = Instant::now(); // Channel that delivers messages periodically. let ticker = tick(Duration::from_millis(50)); // Channel that delivers a single message // after a certain duration of time. let timeout = after(Duration::from_secs(1)); loop { // `select` wait until any one of the channels becomes ready and // execute it. select! { recv(ticker) -> _ => println!("elapsed: {:?}", start.elapsed()), recv(timeout) -> _ => break, // or use: default(Duration::from_millis(1000)) => break, } } }
flume
Smaller and simpler than crossbeam-channel
and almost as fast.
fn main() { todo!(); }
tokio
Tokio's sync
module provides channels for using in async code.
fn main() { todo!(); }
See also
Shared-State Concurrency
Recipe | Crates | Categories |
---|---|---|
Maintain a global mutable state | ||
Mutexes | ||
parking_lot | ||
Atomics | ||
arc-swap |
Channels are similar to single ownership, because once you transfer a value down a channel, you should no longer use that value. Shared memory concurrency is like multiple ownership: multiple threads can access the same memory location at the same time.
The Rust standard library provides smart pointer types, such as Mutex<T>
and Arc<T>
, that are safe to use in concurrent contexts.
Maintain a global mutable state
Declare global state using lazy static
. lazy static
⮳ creates a globally available static ref
which requires a std::sync::Mutex
⮳ to allow mutation (also see std::sync::RwLock
⮳). The std::sync::Mutex
⮳ wrap ensures the state cannot be simultaneously accessed by multiple threads, preventing race conditions. A std::sync::MutexGuard
⮳ must be acquired to read or mutate the value stored in a std::sync::Mutex
⮳.
use std::sync::Mutex; use anyhow::Result; use anyhow::anyhow; use lazy_static::lazy_static; lazy_static! { static ref FRUIT: Mutex<Vec<String>> = Mutex::new(Vec::new()); } fn insert(fruit: &str) -> Result<()> { let mut db = FRUIT .lock() .map_err(|_| anyhow!("Failed to acquire MutexGuard"))?; db.push(fruit.to_string()); Ok(()) } fn main() -> Result<()> { insert("apple")?; insert("orange")?; insert("peach")?; { let db = FRUIT .lock() .map_err(|_| anyhow!("Failed to acquire MutexGuard"))?; db.iter().enumerate().for_each(|(i, item)| { println!("{}: {}", i, item); }); } insert("grape")?; Ok(()) }
Mutexes
Allow access to data from one thread at a time.
use std::sync::Arc; use std::sync::Mutex; use std::thread; fn main() { // We wrap Mutex in Arc to allow for multiple owners. // Arc<T> is safe to use in concurrent situations. let counter = Arc::new(Mutex::new(0)); let mut handles = vec![]; for _ in 0..10 { // `clone` is somewhat a misnomer; it creates another pointer to the // same Mutex, increasing the strong reference count. let counter = Arc::clone(&counter); let handle = thread::spawn( move || { let mut num = counter.lock().unwrap(); *num += 1; }, /* Releases the lock automatically when the MutexGuard * goes out of scope. */ ); handles.push(handle); } for handle in handles { handle.join().unwrap(); } println!("Result: {}", *counter.lock().unwrap()); }
parking_lot
More compact and efficient implementations of the standard synchronization primitives.
parking_lot
⮳ provides implementations of parking_lot::Mutex
⮳, parking_lot::RwLock
⮳, parking_lot::Condvar
⮳ and parking_lot::Once
⮳ that are smaller, faster and more flexible than those in the Rust standard library. It also provides a parking_lot::ReentrantMutex
⮳ type.
std::sync::Mutex
works fine, but parking_lot
is faster.
use parking_lot::Once; use parking_lot::OnceState; // `Once` is a synchronization primitive which can be used to run a one-time // initialization. static INIT: Once = Once::new(); static mut VAL: usize = 0; // This function will only call `expensive_computation` once, and will // otherwise always return the value returned from the first invocation. fn get_cached_val() -> usize { // Accessing a `static mut` is unsafe much of the time, unless we do so // in a synchronized fashion (e.g. write once or read all) unsafe { // The given closure will be executed if this is the first time // call_once has been called. INIT.call_once(|| { VAL = expensive_computation(); println!("This is printed only once!"); }); // A closure has completed successfully. assert_eq!(INIT.state(), OnceState::Done); VAL } } fn expensive_computation() -> usize { // ... 42 } fn main() { // A closure has not been executed yet assert_eq!(INIT.state(), OnceState::New); for _ in 0..3 { assert_eq!(get_cached_val(), 42); } }
use parking_lot::RwLock; fn main() { let lock = RwLock::new(5); { println!("Many reader locks can be held at once"); let r1 = lock.read(); let r2 = lock.read(); assert_eq!(*r1, 5); assert_eq!(*r2, 5); println!("Read locks are dropped at this point"); } { println!("Only one write lock may be held, however"); let mut w = lock.write(); *w += 1; assert_eq!(*w, 6); println!("Write lock is dropped here"); } }
Atomics
Atomic types in std::sync::atomic
⮳ provide primitive shared-memory communication between threads, and are the building blocks of other concurrent types. It defines atomic versions of a select number of primitive types, including std::sync::atomic::AtomicBool
⮳, std::sync::atomic::AtomicIsize
⮳, std::sync::atomic::AtomicUsize
⮳, std::sync::atomic::AtomicI8
⮳, std::sync::atomic::AtomicU16
⮳, etc.
use std::sync::atomic::AtomicUsize; use std::sync::atomic::Ordering; static GLOBAL_THREAD_COUNT: AtomicUsize = AtomicUsize::new(0); fn main() { let old_thread_count = GLOBAL_THREAD_COUNT.fetch_add(1, Ordering::SeqCst); println!("live threads: {}", old_thread_count + 1); }
The most common way to share an atomic variable is to put it into an std::sync::Arc
⮳ (an atomically-reference-counted shared pointer).
crossbeam
⮳ also offers crossbeam::atomic::AtomicCell
⮳, a thread-safe mutable memory location. This type is equivalent to std::cell::Cell
⮳, except it can also be shared among multiple threads.
use crossbeam_utils::atomic::AtomicCell; fn main() { let a = AtomicCell::new(7); let v = a.into_inner(); assert_eq!(v, 7); println!("{}", v); }
arc-swap
The ArcSwap
type is a container for an Arc
that can be changed atomically. Semantically, it is similar to Atomic<Arc<T>>
(if there was such a thing) or RwLock<Arc<T>>
(but without the need for the locking). It is optimized for read-mostly scenarios, with consistent performance characteristics.
Concurrent Data Structures
Recipe | Crates | Categories |
---|---|---|
Bounded multi-producer multi-consumer queue | ||
dashmap | ||
flurry |
dashmap
Fast concurrent HashMap for Rust.
dashmap
⮳ is an implementation of a concurrent associative array / hashmap in Rust. dashmap
⮳ tries to be a direct replacement for RwLock<HashMap<K, V>>
.
use std::sync::Arc;
use std::thread;
use dashmap::DashMap;
fn main() {
// Create a shared DashMap with an Arc
let map: Arc<DashMap<&str, i32, _>> = Arc::new(DashMap::new());
// or use: DashMap::with_capacity(20)
// Create multiple threads
let mut threads = Vec::new();
for i in 0..4 {
let map_clone = map.clone();
let thread_id = i;
threads.push(thread::spawn(move || {
// Access and modify the map from each thread
match thread_id {
0 => {
map_clone.insert("key1", thread_id);
println!("Thread {} inserted key1", thread_id);
}
1 => {
map_clone.insert("key2", thread_id);
println!("Thread {} inserted key2", thread_id);
}
2 => match map_clone.get("key1") {
Some(value) => {
println!("Thread {} read key1: {}", thread_id, *value);
}
_ => {
println!("Thread {} couldn't find key1", thread_id);
}
},
3 => match map_clone.get_mut("key2") {
Some(mut value) => {
*value += 10;
println!(
"Thread {} incremented key2 value to {}",
thread_id, *value
);
}
_ => {
println!("Thread {} couldn't find key2", thread_id);
}
},
_ => panic!("Unknown thread ID"),
}
}));
}
// Wait for all threads to finish
for thread in threads {
thread.join().unwrap();
}
assert_eq!(map.remove("key1").unwrap().1, 0); // returns Option<(K, V)>
assert!(map.contains_key("key2"));
map.remove_if("key2", |_, val| *val == 11);
// Access the final state of the map from the main thread
println!("final count: {}", map.iter().count());
}
Bounded multi-producer multi-consumer queue
Concurrent queues.
use crossbeam_queue::ArrayQueue; fn main() { let q = ArrayQueue::new(2); assert_eq!(q.push('a'), Ok(())); assert_eq!(q.push('b'), Ok(())); assert_eq!(q.push('c'), Err('c')); assert_eq!(q.pop(), Some('a')); println!("{:?}", q.pop()); }
flurry
flurry
is particularly good for read-heavy workloads.
Refer to the comparative benchmarks of concurrent HashMaps⮳ as well.
fn main() {}
Send, Sync traits
Recipe | Crates | Categories |
---|---|---|
Send and Sync traits |
Send
and Sync
traits
The Send
and Sync
traits are fundamental to Rust's concurrency. You can think of Send as "Exclusive access is thread-safe," and Sync as "Shared access is thread-safe."
A type is Send
if it can be transferred across thread boundaries. Most types in Rust are Send
by default, as long as they don't contain non-Send
types.
Send
allows an object to be used by two or more threads at different times. Thread 'A' can create and use an object, then send it to thread 'B', so thread 'B' can use the object while thread 'A' cannot. The Rust ownership model can be used to enforce this non-overlapping use. In other words, Send
means that a type is safe to move from one thread to another. If the same type also implements Copy
, it is safe to copy from one thread to another.
An important exception is Rc
. By cloning, it allows data to have multiple owners. If one owner in thread 'A' could send the Rc
to another thread, giving ownership to thread 'B', there could be other owners in thread 'A' that can still use the object. Since the reference count is modified non-atomically, the value of the count on the two threads may get out of sync and one thread may drop the pointed-at value while there are owners in the other thread. Therefore Rc
does not implement Send
.
A type is Sync
if it is safe to be referenced from multiple threads simultaneously. This is trivial for immutable objects, but mutations need to be synchronized (performed in sequence with the same order being seen by all threads). This is often done using a Mutex
or RwLock
which allows one thread to proceed while others must wait. By enforcing a shared order of changes, these types can turn a non-Sync
object into a Sync
object. Another mechanism for making objects Sync
is to use atomic types, which are essentially Sync
primitives.
Arc
is an Rc
that uses an atomic type for the reference count. Hence it can be used by multiple threads without the count getting out of sync. If the data that the Arc points to is Sync
, the entire object is Sync
. If the data is not Sync
(e.g. a mutable type), it can be made Sync
using a Mutex
. Hence the proliferation of Arc<Mutex<T>>
types in multi-threaded Rust code.
T
is Sync
if and only if &T
is Send
.
use std::sync::Arc; use std::sync::Mutex; use std::thread; fn main() { // Using Arc (Atomic Reference Counting) and Mutex (Mutual Exclusion) // to safely share data between threads. let data = Arc::new(Mutex::new(0)); let mut handles = vec![]; for _ in 0..3 { let data = Arc::clone(&data); // Create 3 threads, each of which increments the shared data by 1 let handle = thread::spawn(move || { let mut num = data.lock().unwrap(); *num += 1; }); handles.push(handle); } // Wait for all threads to finish for handle in handles { handle.join().unwrap(); } println!("Result: {}", *data.lock().unwrap()); }
Existing implementations of Send
and Sync
Traits | Types |
---|---|
Send and Sync | primitives; (T1, T2) , [T; N] , &[T] , struct { x: T } , Arc , Vec , Box , Option (depending on underlying types); String , &str ; Mutex , Atomic* ... |
!Send and !Sync | Rc , raw pointers *const T ,*mut T , types from external libraries or the operating system that are not thread safe |
Send and !Sync | mpsc::Receiver<T> ; UnsafeCell , Cell , RefCell : when a type has interior mutability, we must be sure that we mutate it from one place only, but this place can be everywhere as long as it is singular |
!Send and Sync (rare) | RwLockReadGuard , RwWriteGuard and MutexGuard ; &mut T if T is !Send ; structs which use thread-local storage and accesses that info in Drop |
Implementing Send
and Sync
As discussed above, Send
and Sync
are automatically derived traits. This means that, unlike almost every other trait, if a type is composed entirely of Send
or Sync
types, then it is Send
or Sync
.
If you want to work with non-Sync
/ Send
types like raw pointers, you should build an abstraction on which Send
and Sync
can be derived.
Note that, by implementing the unsafe marker traits Send
and Sync
, you guarantee that your struct can be sent across threads safely. This means the usage of MyStruct
must not cause data races or other thread safety issues. An incorrect implementation can cause Undefined Behavior. Caveat lector!
Send / Sync custom implementation: finish example in playground (P1 / P2)
TODO P1
-
Credit Understanding the Send trait
-
Reference https://limpet.net/mbrubeck/2019/02/07/rust-a-unique-perspective.html https://nyanpasu64.gitlab.io/blog/an-unsafe-tour-of-rust-s-send-and-sync/
nomicon/send-and-sync extensible-concurrency-sync-and-send Implementing Vec: https://doc.rust-lang.org/nomicon/vec/vec.html https://nyanpasu64.gitlab.io/blog/an-unsafe-tour-of-rust-s-send-and-sync/ https://limpet.net/mbrubeck/2019/02/07/rust-a-unique-perspective.html
-
Need deep tech review
-
Add Send / Sync impl example. See code in playground crate:
In the following example, we define a struct with a raw pointer to some data.
We spawn multiple threads to demonstrate that MyStruct
is Send
and Sync
. Each thread prints the value of the data pointer.
The data pointer remains valid and that the usage of MyStruct
is thread-safe.
{{#include ../../../crates/ex/categories/c/tests/concurrency/sync_send_impl.rs:example}}
Configuration
Facilitate configuration management for applications.
Configuration Management
Environment Variables
Environment variables
dotenvy
dotenvy
⮳ forks and supersedes dotenv
⮳.
use std::env;
use anyhow::Result;
fn main() -> Result<()> {
// Load environment variables from .env file.
// Fails if .env file not found, not readable or invalid.
// If variables with the same names already exist in the environment,
// their values will be preserved. If multiple declarations for the
// same environment variable exist in your .env file, the first one is
// applied.
dotenvy::dotenv()?;
// OR: dotenvy::dotenv().ok();
for (key, value) in env::vars() {
println!("{key}: {value}");
}
Ok(())
}
std::env
To retrieve a single environment variable,
use std::env; fn env_extract() -> String { let log_env_var = env::var("RUST_LOG").unwrap_or_else(|_| "debug".into()); println!("RUST_LOG: {log_env_var}"); let user_env_var = env::var("USER").expect("$USER is not set"); println!("USER: {user_env_var}"); // Inspect an environment variable at compile-time. // Uncomment to test. // let shell = env!("SHELL", "$SHELL is not set"); let optional_value = option_env!("SHELL"); optional_value.unwrap_or("no shell set").to_string() } fn main() { println!("SHELL: {}", env_extract()); }
Working with environment variables in Rust⮳
envy
envy
can deserialize environment variables into type-safe structs.
[dependencies]
envy = "0.4"
serde = { version = "1.0.216", features = ["derive"] }
use serde::Deserialize;
#[derive(Deserialize, Debug)]
struct Configuration {
port: u16,
}
fn main() {
let c = envy::from_env::<Configuration>()
.expect("Please provide the PORT env variable");
let c2 = envy::prefixed("MY_APP__")
.from_env::<Configuration>()
.expect("Please provide MY_APP__PORT env variable");
println!("c: {:?} c2: {:?}", c, c2);
}
See Also
Configuration
config
config
⮳ is a layered configuration system for Rust applications. It reads from JSON, TOML, YAML, INI, RON, JSON5 files.
fn main() { todo!(); }
confy
use serde::Deserialize;
use serde::Serialize;
#[derive(Serialize, Deserialize, Debug)]
struct MyConfig {
version: u8,
api_key: String,
}
/// `MyConfig` implements `Default`
impl ::std::default::Default for MyConfig {
fn default() -> Self {
Self {
version: 0,
api_key: "".into(),
}
}
}
fn main() -> Result<(), confy::ConfyError> {
let cfg: MyConfig = confy::load("my-app-name", None)?;
// confy::store("my-app-name", None, cfg)?;
println!("{:?}", cfg);
Ok(())
}
Cryptography
Securing data.
Encryption
Recipe | Crates | Categories |
---|---|---|
Salt and hash a password with PBKDF2 |
Hashing
- ring
- rust-crypto
sodiumoxide
Fast cryptographic library for Rust (bindings to libsodium)
Hashing
Calculate the SHA-256 digest of a file
Writes some data to a file, then calculates the SHA-256 digest::Digest
⮳ of the file's contents using digest::Context
⮳
use std::fs; use std::fs::File; use std::io::BufReader; use std::io::Read; use std::io::Write; use anyhow::Result; use data_encoding::HEXUPPER; use ring::digest::Context; use ring::digest::Digest; use ring::digest::SHA256; fn sha256_digest<R: Read>(mut reader: R) -> Result<Digest> { let mut context = Context::new(&SHA256); let mut buffer = [0; 1024]; loop { let count = reader.read(&mut buffer)?; if count == 0 { break; } context.update(&buffer[..count]); } Ok(context.finish()) } fn main() -> Result<()> { if !fs::exists("temp")? { fs::create_dir("temp")?; } let path = "temp/file.txt"; let mut output = File::create(path)?; write!(output, "We will generate a digest of this text")?; let input = File::open(path)?; let reader = BufReader::new(input); let digest = sha256_digest(reader)?; println!("SHA-256 digest is {}", HEXUPPER.encode(digest.as_ref())); Ok(()) }
Sign and verify a message with a HMAC digest
Uses ring::hmac
⮳ to creates a ring::signature::Signature
⮳ of a string then verifies the signature is correct.
use ring::error::Unspecified; use ring::hmac; use ring::rand; use ring::rand::SecureRandom; fn main() -> Result<(), Unspecified> { // Create a key let mut key_value = [0u8; 48]; let rng = rand::SystemRandom::new(); rng.fill(&mut key_value)?; let key = hmac::Key::new(hmac::HMAC_SHA256, &key_value); // Sign a message let message = "Legitimate and important message."; let signature = hmac::sign(&key, message.as_bytes()); // Calculates the HMAC of data using the signing key key, // and verifies whether the resultant value equals the signature. hmac::verify(&key, message.as_bytes(), signature.as_ref())?; println!("Message verified."); Ok(()) }
Use general-purpose hashing algorithms
For more algorithms, see Rust Crypto Hashes: sha2, sha1, md-5
Pure Rust implementation of the SHA-2 hash function family, including SHA-224, SHA-256, SHA-384, and SHA-512.
fn main() { todo!(); }
SHA-1 hash function
fn main() { todo!(); }
MD5 hash function
fn main() { todo!(); }
Encrypt with AEAD
For more algorithms, see Rust Crypto AEADs: aes-gcm-siv, aes-gcm, chacha20poly1305
Pure Rust implementation of the AES-GCM-SIV Misuse-Resistant Authenticated Encryption Cipher (RFC 8452) with optional architecture-specific hardware acceleration.
fn main() { todo!(); }
Pure Rust implementation of the AES-GCM (Galois/Counter Mode) Authenticated Encryption with Associated Data (AEAD) Cipher with optional architecture-specific hardware acceleration.
fn main() { todo!(); }
Use the RSA algorithm
Pure Rust RSA implementation.
fn main() { todo!(); }
Compute digital signatures
For more algorithms, see Rust Crypto Signatures:
Edwards Digital Signature Algorithm (EdDSA) over Curve25519 (as specified in RFC 8032) support library providing signature type definitions and PKCS#8 private key decoding/encoding support.
fn main() { todo!(); }
Fast and efficient ed25519 EdDSA key generations, signing, and verification in pure Rust.
Pure Rust implementation of the Elliptic Curve Digital Signature Algorithm (ECDSA) as specified in FIPS 186-4 (Digital Signature Standard), providing RFC6979 deterministic signatures as well as support for added entropy.
fn main() { todo!(); }
Pure Rust implementation of the Digital Signature Algorithm (DSA) as specified in FIPS 186-4 (Digital Signature Standard), providing RFC6979 deterministic signatures as well as support for added entropy.
fn main() { todo!(); }
Create certificates
For more formats, see Rust Crypto Formats.
Pure Rust embedded-friendly implementation of the Distinguished Encoding Rules (DER) for Abstract Syntax Notation One (ASN.1) as described in ITU X.690 with full support for heapless no_std
targets.
fn main() { todo!(); }
PEM Encoding (RFC 7468) for PKIX, PKCS, and CMS Structures, implementing a strict subset of the original Privacy-Enhanced Mail encoding intended specifically for use with cryptographic keys, certificates, and other messages. Provides a no_std-friendly, constant-time implementation suitable for use with cryptographic private keys.
fn main() { todo!(); }
Pure Rust implementation of Public-Key Cryptography Standards (PKCS) #8: Private-Key Information Syntax Specification (RFC 5208), with additional support for PKCS#8v2 asymmetric key packages (RFC 5958).
fn main() { todo!(); }
Pure Rust implementation of the X.509 Public Key Infrastructure Certificate format as described in RFC 5280.
fn main() { todo!(); }
Use TLS / SSL
Rustls is a portable pure-rust high-level implementation of TLS. Implements TLS 1.2 and higher.
fn main() { todo!(); }
A wrapper over a platform's native TLS implementation. Delegates to the system TLS implementations on windows and macOS, and uses OpenSSL on Linux.
fn main() { todo!(); }
Utilities
subtle
Pure-Rust traits and utilities for constant-time cryptographic implementations.
fn main() { todo!(); }
zeroize
Securely clear secrets from memory with a simple trait built on stable Rust primitives which guarantee memory is zeroed using an operation that will not bee optimized away by the compiler. Uses a portable pure Rust implementation that works everywhere, even WASM.
fn main() { todo!(); }
Encryption
Recipe | Crates | Categories |
---|---|---|
Salt and hash a password with PBKDF2 |
Salt and hash a password with PBKDF2
Uses ring::pbkdf2
⮳ to hash a salted password using the PBKDF2 key derivation function ring::pbkdf2::derive
⮳
Verifies the hash is correct with ring::pbkdf2::verify
⮳
The salt is generated using ring::rand::SecureRandom::fill
⮳ which fills the salt byte array with securely generated random numbers.
use std::num::NonZeroU32; use data_encoding::HEXUPPER; use ring::digest; use ring::error::Unspecified; use ring::pbkdf2; use ring::rand; use ring::rand::SecureRandom; fn main() -> Result<(), Unspecified> { const CREDENTIAL_LEN: usize = digest::SHA512_OUTPUT_LEN; let n_iter = NonZeroU32::new(100_000).unwrap(); let rng = rand::SystemRandom::new(); let mut salt = [0u8; CREDENTIAL_LEN]; rng.fill(&mut salt)?; let password = "Guess Me If You Can!"; let mut pbkdf2_hash = [0u8; CREDENTIAL_LEN]; pbkdf2::derive( pbkdf2::PBKDF2_HMAC_SHA512, n_iter, &salt, password.as_bytes(), &mut pbkdf2_hash, ); println!("Salt: {}", HEXUPPER.encode(&salt)); println!("PBKDF2 hash: {}", HEXUPPER.encode(&pbkdf2_hash)); let should_succeed = pbkdf2::verify( pbkdf2::PBKDF2_HMAC_SHA512, n_iter, &salt, password.as_bytes(), &pbkdf2_hash, ); assert!(should_succeed.is_ok()); let wrong_password = "Definitely not the correct password"; let should_fail = pbkdf2::verify( pbkdf2::PBKDF2_HMAC_SHA512, n_iter, &salt, wrong_password.as_bytes(), &pbkdf2_hash, ); assert!(should_fail.is_err()); Ok(()) }
Data Structures
Rust implementations of ways of organizing data suited for specific purposes.
Bitflags
Recipe | Crates | Categories |
---|---|---|
Define and operate on a type represented as a bitfield | ||
flagset |
Hashmaps and friends
Recipe | Crates | Categories |
---|---|---|
Store data in an insertion-ordered map | ||
Store data in a multimap | ||
slotmap |
Stack-allocated arrays
UUIDs
Recipe | Crates | Categories |
---|---|---|
Generate and parse UUIDs |
Hashset BinaryHeap LinkedList Stack Queue BTreeMap BTreeSet
Alongside arrayvec and tinyvec, heapless
has stack-allocated arrays, but also includes:
Arc – like std::sync::Arc but backed by a lock-free memory pool rather than #[global_allocator]
Box – like std::boxed::Box but backed by a lock-free memory pool rather than #[global_allocator]
BinaryHeap – priority queue
IndexMap – hash table
IndexSet – hash set
LinearMap
Object – objects managed by an object pool
String
Vec
mpmc::Q* – multiple producer multiple consumer lock-free queue
spsc::Queue – single producer single consumer lock-free queue
Custom
Recipe | Crates | Categories |
---|---|---|
Define and operate on a type represented as a bitfield | ||
flagset |
Define and operate on a type represented as a bitfield
bitflags
offers a macro to generate structures which behave like bitflags. It creates type-safe bitfield type MyFlags
with help of bitflags::bitflags
⮳ macro and implements elementary clear
operation as well as std::fmt::Display
⮳ trait for it. Subsequently, shows basic bitwise operations and formatting.
use std::fmt; use bitflags::bitflags; bitflags! { // Attributes can be applied to flags types #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] struct MyFlags: u32 { const FLAG_A = 0b0000_0001; const FLAG_B = 0b0000_0010; const FLAG_C = 0b0000_0100; const FLAG_ABC = Self::FLAG_A.bits() | Self::FLAG_B.bits() | Self::FLAG_C.bits(); } } impl MyFlags { pub fn as_u64(&self) -> u64 { self.bits() as u64 } } impl fmt::Display for MyFlags { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{:032b}", self.bits()) } } 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 fmt::Display implementation above println!("e1: {} e2: {}", e1, e2); let flags = MyFlags::FLAG_ABC; assert_eq!(format!("{}", flags), "00000000000000000000000000000111"); assert_eq!(format!("{:?}", MyFlags::FLAG_B), "MyFlags(FLAG_B)"); assert_eq!( format!("{:?}", MyFlags::FLAG_A | MyFlags::FLAG_B), "MyFlags(FLAG_A | FLAG_B)" ); println!("{:?}", flags); }
flagset
FlagSet is a new, ergonomic approach to handling flags that combines the best of existing crates like bitflags
and enumflags
without their downsides.
use std::os::raw::c_int;
use flagset::flags;
flags! {
enum FlagsA: u8 {
Foo,
Bar,
Baz,
}
enum FlagsB: c_int {
Foo,
Bar,
Baz,
}
}
fn main() {}
Hashmap's friends
Recipe | Crates | Categories |
---|---|---|
Store data in an insertion-ordered map | ||
Store data in a multimap | ||
slotmap |
Store data in an insertion-ordered map
indexmap
offers a hash map that separately keeps track of insertion order and allows you to efficiently iterate over its elements in that order.
use indexmap::IndexMap; // The indexmap crate in Rust provides a hash table // where the keys have a consistent order of insertion, // which is preserved when iterating. fn main() { // Creating an IndexMap let mut map = IndexMap::new(); // Inserting elements map.insert("a", 1); map.insert("b", 2); map.insert("c", 3); // Iterating in insertion order println!("Iterating over IndexMap in insertion order:"); for (key, value) in &map { println!("{}: {}", key, value); } // Accessing elements by index if let Some((key, value)) = map.get_index(1) { println!("Element at index 1: {}: {}", key, value); } // Using the `entry` API map.entry("d").or_insert(4); map.entry("a").or_insert(10); // This won't change "a" because it already exists println!("IndexMap after using entry API:"); for (key, value) in &map { println!("{}: {}", key, value); } }
Store data in a multimap
multimap
is implemented as a thin wrapper around std::collections::HashMap
. It allows multiple values for a given key.
use anyhow::Result;
use crates_io_api::Category;
use crates_io_api::SyncClient;
use multimap::MultiMap;
// Calls the crates.io API client and retrieve the categories a given crate
// belongs to.
fn get_categories_for_crate(crate_name: &str) -> Result<Vec<Category>> {
let client = SyncClient::new(
"my-user-agent (my-contact@domain.com)",
std::time::Duration::from_millis(1000), // Rate limit interval
)?;
// Retrieve the crate's information
let crt = client.get_crate(crate_name)?;
Ok(crt.categories)
}
fn main() -> Result<()> {
let crate_names = vec!["toml", "config", "nom", "pest"];
let mut m: MultiMap<String, &str> = MultiMap::new();
for name in crate_names {
for cat in get_categories_for_crate(name)? {
// There can be multiple crates in the same category
// A multimap allows multiple values for the same key
m.insert(cat.slug, name);
}
}
// Get all values for a given key
println!(
"List of crates in the `config` category: {:?}",
m.get_vec("config")
);
// Or iterate over all keys and the key's vector
for (cat, names) in m.iter_all() {
println!("Category: {:?}, names: {:?}", cat, names);
}
Ok(())
}
slotmap
Use to store collections of objects that need stable, safe references but have no clear ownership otherwise, such as game entities or graph nodes.
slotmap
provides three containers with persistent unique keys to access stored values, SlotMap
, HopSlotMap
and DenseSlotMap
. Upon insertion a key is returned that can be used to later access or remove the values. Insertion, deletion and access all take O(1) time with low overhead. Two secondary maps, SecondaryMap
and SparseSecondaryMap
are also provided that map further objects to the keys created by one of the slot maps.
fn main() {
todo!();
}
See also
Stack-allocated arrays
arrayvec
Arrays that are ONLY stack-allocated with fixed capacity.
use arrayvec::ArrayVec;
// ArrayVec is a vector backed by a fixed size array.
// The capacity is of type usize but is range limited to u32::MAX
// It offers a simple API but also dereferences to a slice, so that the full
// slice API is available.
fn main() {
let mut array = ArrayVec::<_, 2>::new();
assert_eq!(array.capacity(), 2);
// Push some elements into the ArrayVec
array.push(1);
array.push(2);
assert!(array.is_full());
// Trying to push beyond the capacity will result in a panic
// ERROR: array.push(3);
let overflow = array.try_push(3);
assert!(overflow.is_err());
// Access elements
for i in 0..array.len() {
println!("Element at index {}: {}", i, array[i]);
}
assert_eq!(&array[..], &[1, 2]);
let mut array2: ArrayVec<i32, 3> = ArrayVec::from([1, 2, 3]);
// Pop an element from the ArrayVec
if let Some(value) = array2.pop() {
println!("Popped value: {}", value);
}
assert_eq!(array2.len(), 2);
assert!(!array2.is_empty());
}
smallvec
Arrays that are stack-allocated with fallback to the heap if the fixed stack capacity is exceeded.
use smallvec::SmallVec;
use smallvec::smallvec;
fn main() {
// Create a SmallVec with a small inline capacity of 4
let mut small_vec: SmallVec<i32, 4> = SmallVec::new();
// Push some elements into the SmallVec
small_vec.push(1);
small_vec.push(2);
small_vec.push(3);
small_vec.push(4);
// You can also initialize it via a macro:
let mut small_vec: SmallVec<i32, 4> = smallvec![1, 2, 3, 4];
// Print the current state of the SmallVec
println!("SmallVec (inline): {:?}", small_vec);
// Push beyond the inline capacity, causing a heap allocation
small_vec.push(5);
// Print the state of the SmallVec after pushing beyond capacity
println!("SmallVec (heap-allocated): {:?}", small_vec);
// Access elements
for i in 0..small_vec.len() {
println!("Element at index {}: {}", i, small_vec[i]);
}
// Pop an element from the SmallVec
if let Some(value) = small_vec.pop() {
println!("Popped value: {}", value);
}
// Print the state of the SmallVec after popping
println!("SmallVec after popping: {:?}", small_vec);
// Split off the SmallVec
let mut small_vec2 = small_vec.split_off(1);
assert_eq!(small_vec, [1]);
assert_eq!(small_vec2, [2, 3, 4]);
// SmallVec points to a slice, so you can use normal slice
// indexing and other methods to access its contents:
small_vec2[0] = small_vec2[1] + small_vec2[2];
small_vec2.sort();
}
tinyvec
The tinyvec
crate provides a way to work with vectors that can store a small number of elements inline, without heap allocation, and dynamically grow to the heap if necessary. It is in 100% safe Rust code. It's similar to smallvec
but with a smaller feature set and no dependencies. tinyvec
requires items to implement the Default
trait.
use tinyvec::TinyVec;
fn main() {
// Create a TinyVec with an inline capacity of 4 elements
let mut tiny_vec: TinyVec<[i32; 4]> = TinyVec::new();
// Push some elements into the TinyVec
tiny_vec.push(1);
tiny_vec.push(2);
tiny_vec.push(3);
tiny_vec.push(4);
// Print the current state of the TinyVec
println!("TinyVec (inline): {:?}", tiny_vec);
// Push beyond the inline capacity, causing a heap allocation
tiny_vec.push(5);
// Print the state of the TinyVec after pushing beyond capacity
println!("TinyVec (heap-allocated): {:?}", tiny_vec);
// Access elements
for i in 0..tiny_vec.len() {
println!("Element at index {}: {}", i, tiny_vec[i]);
}
// Pop an element from the TinyVec
if let Some(value) = tiny_vec.pop() {
println!("Popped value: {}", value);
}
// Print the state of the TinyVec after popping
println!("TinyVec after popping: {:?}", tiny_vec);
}
UUID
Recipe | Crates | Categories |
---|---|---|
Generate and parse UUIDs |
A UUID is a unique 128-bit value, stored as 16 octets, and regularly formatted as a hex string in five groups. UUIDs are used to assign unique identifiers to entities without requiring a central allocating authority. They are particularly useful in distributed systems, though can be used in disparate areas, such as databases and network protocols.
Generate and parse UUIDs
uuid
generates and parses UUIDs and implements a number of utility functions.
use uuid::Uuid; use uuid::uuid; fn main() { // Generate a new UUID (version 4) let my_uuid = Uuid::new_v4(); println!("Generated UUID: {}", my_uuid); // Parse a UUID from a string let uuid_str = "550e8400-e29b-41d4-a716-446655440000"; match Uuid::parse_str(uuid_str) { Ok(parsed_uuid) => println!("Parsed UUID: {}", parsed_uuid), Err(e) => println!("Failed to parse UUID: {}", e), } // Use a macro const ID: Uuid = uuid!("67e55044-10b1-426f-9247-bb680e5fe0c8"); // Print as a URN println!("{}", ID.urn()); // Compare UUIDs let another_uuid = Uuid::new_v4(); if my_uuid == another_uuid { println!("The UUIDs are equal."); } else { println!("The UUIDs are different."); } }
Databases
Interface with database management systems.
SQLite
Recipe | Crates | Categories |
---|---|---|
Create a SQLite database | ||
Insert and select data | ||
Using transactions |
Postgres
Recipe | Crates | Categories |
---|---|---|
Create tables in a Postgres database | ||
Insert and query data | ||
Aggregate data | ||
tokio-postgres | ||
cornucopia for postgres | cornucopia-rs⮳ |
Connection pools
Recipe | Crates | Categories |
---|---|---|
Create a connection pool |
NoSQL and friends
Recipe | Crates | Categories |
---|---|---|
Connect to MongoDB | ||
Connect to Redis |
Search
Recipe | Crates | Categories |
---|---|---|
Connect to Elasticsearch | ||
infisearch | ||
stork-search | ||
minisearch | ||
typesense | ||
tinysearch |
Query builders and ORMs
SQLite
Recipe | Crates | Categories |
---|---|---|
Create a SQLite database | ||
Insert and select data | ||
Using transactions |
rusqlite
provides an API to SQLite and gives access to advanced SQlite features.
Create a SQLite database
Use the rusqlite
⮳ crate to open SQLite databases. See
the documentation⮳ for compiling on Windows.
rusqlite::Connection::open
⮳ will create the database if it doesn't already exist.
use std::fs; use rusqlite::Connection; pub fn main() -> anyhow::Result<()> { if !fs::exists("temp")? { fs::create_dir("temp")?; } let conn = Connection::open("temp/cats.db")?; conn.execute( "create table if not exists cat_colors ( id integer primary key, name text not null unique )", (), // Empty list of parameters. )?; 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) )", (), // Empty list of parameters. )?; println!("Tables created."); Ok(()) }
Insert and select data
rusqlite::Connection::open
⮳ will open the database cats
created in the earlier recipe. This recipe inserts data into cat_colors
and cats
tables using the rusqlite::Connection::execute
⮳ method of rusqlite::Connection
⮳. First, the data is inserted into the cat_colors
table. After a record for a color is inserted, rusqlite::Connection::last_insert_rowid
⮳ method of rusqlite::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 rusqlite::Connection::prepare
⮳ method which gives a rusqlite::Statement
⮳ struct. Then, query is executed using rusqlite::Statement::query_map
⮳ method of rusqlite::Statement
⮳
use std::collections::HashMap;
use rusqlite::Connection;
use rusqlite::Result;
#[derive(Debug)]
#[allow(dead_code)]
struct Cat {
name: String,
color: String,
}
pub fn main() -> Result<()> {
let conn = Connection::open("temp/cats.db")?;
let mut cat_colors = HashMap::new();
cat_colors.insert(String::from("Blue"), vec!["Tigger", "Sammy"]);
cat_colors.insert(String::from("Black"), vec!["Oreo", "Biscuit"]);
for (color, catnames) in &cat_colors {
conn.execute("INSERT INTO cat_colors (name) values (?1)", [
&color.to_string()
])?;
let last_id: String = conn.last_insert_rowid().to_string();
for cat in catnames {
conn.execute(
"INSERT INTO cats (name, color_id) values (?1, ?2)",
[&cat.to_string(), &last_id],
)?;
}
}
let mut stmt = conn.prepare(
"SELECT c.name, cc.name from cats c
INNER JOIN cat_colors cc
ON cc.id = c.color_id;",
)?;
let cats = stmt.query_map([], |row| {
Ok(Cat {
name: row.get(0)?,
color: row.get(1)?,
})
})?;
for cat in cats {
println!("Found cat {:?}", cat);
}
Ok(())
}
Using transactions
rusqlite::Connection::open
⮳ will open the cats.db
database from the top recipe.
Begin a transaction with rusqlite::Connection::transaction
⮳ Transactions will roll back unless committed explicitly with rusqlite::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.
use std::fs;
use anyhow::Result;
use rusqlite::Connection;
pub fn main() -> Result<()> {
if !fs::exists("temp")? {
fs::create_dir("temp")?;
}
let mut conn = Connection::open("temp/cats.db")?;
successful_tx(&mut conn)?;
println!("Successful transaction.");
let res = rolled_back_tx(&mut conn);
assert!(res.is_err());
println!(
"Attempt to insert the same name in a unique column fails. The transaction was rolled-back."
);
Ok(())
}
fn successful_tx(conn: &mut Connection) -> Result<()> {
let tx = conn.transaction()?;
tx.execute("delete from cat_colors", ())?;
tx.execute("insert into cat_colors (name) values (?1)", [&"lavender"])?;
tx.execute("insert into cat_colors (name) values (?1)", [&"blue"])?;
tx.commit()?;
Ok(())
}
fn rolled_back_tx(conn: &mut Connection) -> Result<()> {
let tx = conn.transaction()?;
tx.execute("delete from cat_colors", ())?;
tx.execute("insert into cat_colors (name) values (?1)", [&"lavender"])?;
tx.execute("insert into cat_colors (name) values (?1)", [&"blue"])?;
tx.execute("insert into cat_colors (name) values (?1)", [&"lavender"])?;
tx.commit()?;
Ok(())
}
Working with Postgres
Recipe | Crates | Categories |
---|---|---|
Create tables in a Postgres database | ||
Insert and query data | ||
Aggregate data | ||
tokio-postgres | ||
cornucopia for postgres | cornucopia-rs⮳ |
Create tables in a Postgres database
Use the postgres
⮳ crate to create tables in a Postgres database.
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
.
use postgres::Client;
use postgres::NoTls;
pub fn main() -> anyhow::Result<()> {
// The connection URL is formatted as
// postgresql://<user>:<password>@<host>/<db>, for example "postgresql:/
// /postgres:postgres@localhost/library"
let mut client = Client::connect(
"postgresql://postgres:mysecretpassword@rust_howto_dev-postgres-1/library",
NoTls,
)?;
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
)
",
)?;
println!("Tables created!");
Ok(())
}
Insert and query data
The recipe inserts data into the author
table using postgres::Client::execute
⮳ method of postgres::Client
⮳. Then, displays the data from the author
table using postgres::Client::query
⮳ method of postgres::Client
⮳.
use std::collections::HashMap;
use postgres::Client;
use postgres::Error;
use postgres::NoTls;
struct Author {
_id: i32,
name: String,
country: String,
}
pub fn main() -> Result<(), Error> {
// The connection URL is formatted as
// postgresql://<user>:<password>@<host>/<db>, for example postgresql://
// postgres:postgres@localhost/library
let mut client = Client::connect(
"postgresql://postgres:mysecretpassword@rust_howto_dev-postgres-1/library",
NoTls,
)?;
let mut authors = HashMap::new();
authors.insert(String::from("Chinua Achebe"), "Nigeria");
authors.insert(String::from("Rabindranath Tagore"), "India");
authors.insert(String::from("Anita Nair"), "India");
for (key, value) in &authors {
let author = Author {
_id: 0,
name: key.to_string(),
country: value.to_string(),
};
client.execute(
"INSERT INTO author (name, country) VALUES ($1, $2)",
&[&author.name, &author.country],
)?;
}
for row in client.query("SELECT id, name, country FROM author", &[])? {
let author = Author {
_id: row.get(0),
name: row.get(1),
country: row.get(2),
};
println!("Author {} is from {}", author.name, author.country);
}
Ok(())
}
Aggregate data
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;
use postgres::Error;
use postgres::NoTls;
struct Nation {
nationality: String,
count: i64,
}
// https://github.com/MuseumofModernArt/collection/tree/main
pub fn main() -> Result<(), Error> {
// The connection URL is formatted as
// postgresql://<user>:<password>@<host>/<db>, for example postgresql://
// postgres:postgres@127.0.0.1/moma
let mut client = Client::connect(
"postgresql://postgres:mysecretpassword@rust_howto_dev-postgres-1/moma",
NoTls,
)?;
for row in client.query(
"SELECT nationality, COUNT(nationality) AS count
FROM artists GROUP BY nationality ORDER BY count DESC",
&[],
)? {
let (nationality, count): (Option<String>, Option<i64>) =
(row.get(0), row.get(1));
if nationality.is_some() && count.is_some() {
let nation = Nation {
nationality: nationality.unwrap(),
count: count.unwrap(),
};
println!("{} {}", nation.nationality, nation.count);
}
}
Ok(())
}
tokio-postgres
Postgres-specific library. Performs better than SQLx.
fn main() {
todo!();
}
cornucopia
for postgres
Generate type-checked Rust from your PostgreSQL: cornucopia-rs⮳
Cornucopia is a tool powered by rust-postgres
designed to generate type-checked Rust interfaces from your PostgreSQL queries. It works by preparing your queries against an actual database and then running an extensive validation suite on them. Once the queries are prepared and validated, Rust code is generated into a module, which can be imported and used in your project.
The basic premise is thus to:
- Write PostgreSQL queries.
- Use Cornucopia to generate Rust code.
- Use the generated code in your project.
fn main() {
todo!();
}
Connection pool
Recipe | Crates | Categories |
---|---|---|
Create a connection pool |
Create a connection pool
deadpool
is a simple async pool for connections and objects of any type.
use deadpool::managed; #[derive(Debug)] enum Error { Fail, } struct Server; impl Server { async fn get_answer(&self) -> i32 { 42 } } struct Manager; impl managed::Manager for Manager { type Error = Error; type Type = Server; async fn create(&self) -> Result<Server, Error> { Ok(Server) } async fn recycle( &self, _: &mut Server, _: &managed::Metrics, ) -> managed::RecycleResult<Error> { Ok(()) } } type Pool = managed::Pool<Manager>; #[tokio::main] async fn main() -> anyhow::Result<()> { let mgr = Manager; let pool = Pool::builder(mgr).build()?; let conn = pool.get().await.map_err(|err| { anyhow::anyhow!("Could not retrieve from the Pool: {:?}", err) })?; let answer = conn.get_answer().await; assert_eq!(answer, 42); println!("The answer is {}", answer); Ok(()) }
Query builders and ORMs
sqlx
sqlx
⮳ is the Rust SQL Toolkit. An async, pure Rust SQL crate featuring compile-time checked queries without a DSL. Supports PostgreSQL, MySQL, SQLite, and MSSQL.
Works with Postgres, MySQL, SQLite, and MS SQL. Supports compile time checking of queries. Async: supports both tokio and async-std.
fn main() {
todo!();
}
SeaORM
Built on top of sqlx (see above). There is also a related sea-query crate that provides a query builder without full ORM functionality.
fn main() {
todo!();
}
diesel
Has excellent performance and takes an approach of strict compile time guarantees. The main crate is Sync only, but diesel-async provides an async connection implementation.
fn main() {
todo!();
}
toasty
Toasty is an ORM for the Rust programming language that prioritizes ease-of-use. It supports both SQL datases as well as some NoSQL databases, including DynamoDB and Cassandra. Note that Toasty does not hide the database capabilities. Instead, Toasty exposes features based on the target database.
It is currently in active development and not yet published to crates.io. You can try using it directly from Github.
Using the example in the Toasty announcement blog, projects that use Toasty start by creating a schema file to define the application's data model.
model User {
#[key]
#[auto]
id: Id,
name: String,
#[unique]
email: String,
todos: [Todo],
moto: Option<String>,
}
model Todo {
#[key]
#[auto]
id: Id,
#[index]
user_id: Id<User>,
#[relation(key = user_id, references = id)]
user: User,
title: String,
}
Use the Toasty CLI tool to generate all necessary Rust code for working with this data model.
// Create a new user and give them some todos.
User::create()
.name("John Doe")
.email("john@example.com")
.todo(Todo::create().title("Make pizza"))
.todo(Todo::create().title("Finish Toasty"))
.todo(Todo::create().title("Sleep"))
.exec(&db)
.await?;
// Load the user from the database
let user = User::find_by_email("john@example.com").get(&db).await?
// Load and iterate the user's todos
let mut todos = user.todos().all(&db).await.unwrap();
while let Some(todo) = todos.next().await {
let todo = todo.unwrap();
println!("{:#?}", todo);
}
NoSQL and friends
Recipe | Crates | Categories |
---|---|---|
Connect to MongoDB | ||
Connect to Redis |
Connect to MongoDB
This is the officially supported MongoDB Rust driver, a client side library that can be used to interact with MongoDB deployments in Rust applications. It uses the bson crate for BSON support. The driver contains a fully async API that requires tokio. The driver also has a sync API that may be enabled via feature flags.
use std::env;
use dotenvy::dotenv;
use mongodb::Client;
use mongodb::bson::doc;
use serde::Deserialize;
use serde::Serialize;
#[derive(Debug, Serialize, Deserialize)]
struct User {
name: String,
age: i32,
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
// Read the .env file, if present
dotenv().ok();
// Example: mongodb://root:passwd@server:27017/
let mongo_uri = env::var("MONGO_URI")?;
let client = Client::with_uri_str(&mongo_uri).await?;
let db = client.database("test_db");
let collection = db.collection::<User>("users");
let user = User {
name: String::from("Alice"),
age: 30,
};
collection.insert_one(user).await?;
let filter = doc! { "name": "Alice" };
if let Some(user) = collection.find_one(filter).await? {
println!("Found user: {:?}", user);
} else {
println!("User not found");
}
Ok(())
}
Connect to Redis
Redis-rs is a high level redis library for Rust. It provides convenient access to all Redis functionality through a very flexible but low-level API. It uses a customizable type conversion trait so that any operation can return results in just the type you are expecting. This makes for a very pleasant development experience.
use std::env;
use anyhow::Context;
use anyhow::Result;
use redis::Commands;
use redis::Connection;
fn connect() -> Result<Connection> {
let redis_host_name = env::var("REDIS_HOSTNAME")
.context("missing environment variable REDIS_HOSTNAME")?;
let redis_password = env::var("REDIS_PASSWORD").unwrap_or_default();
// Does Redis server need a secure connection?
let uri_scheme = match env::var("IS_TLS") {
Ok(_) => "rediss",
Err(_) => "redis",
};
// The URL format is
// protocol=<protocol>]] For example, "redis://127.0.0.1/"
let redis_conn_url =
format!("{}://:{}@{}", uri_scheme, redis_password, redis_host_name);
// `open` does not actually open a connection yet, but it does perform some
// basic checks on the URL that might make the operation fail.
Ok(redis::Client::open(redis_conn_url)?.get_connection()?)
}
fn fetch_an_integer() -> Result<isize> {
let mut con = connect()?;
// Throw away the result, just make sure it does not fail
let _: () = con.set("my_key", 42)?;
// Read back the key and return it. Because the return value
// from the function is a result for integer, this will automatically
// convert into one.
Ok(con.get("my_key")?)
}
fn main() -> Result<()> {
let my_int = fetch_an_integer()?;
println!("{}", my_int);
Ok(())
}
Search
Recipe | Crates | Categories |
---|---|---|
Connect to Elasticsearch | ||
infisearch | ||
stork-search | ||
minisearch | ||
typesense | ||
tinysearch |
Connect to Elasticsearch
fn main() {
todo!();
}
infisearch
fn main() {
todo!();
}
stork-search
fn main() {
todo!();
}
minisearch
minisearch-client-side-fulltext-search-engine
fn main() {
todo!();
}
typesense
fn main() {
todo!();
}
tinysearch
A Tiny, Static, Full-Text Search Engine using Rust and WebAssembly
fn main() {
todo!();
}
Date and Time
Manage the complexity of dealing with the fourth dimension.
There are two key libraries:
chrono
: a comprehensive, full-featured, yet complex date and time library,time
: a smaller, simpler library with limited functionality.
There is no clear answer as to which is best between time
and chrono
. Evaluate for yourself between these two, but both are trusted and well-maintained.
Duration and calculation
Recipe | Crates | Categories |
---|---|---|
Measure the elapsed time between two code sections | ||
Perform checked date and time calculations | ||
Convert a local time to another timezone |
Parsing and displaying
Recipe | Crates | Categories |
---|---|---|
Examine the date and time | ||
Convert date to UNIX timestamp and vice versa | ||
Display formatted date and time | ||
Parse string into DateTime struct |
Using the time
crate
Recipe | Crates | Categories |
---|---|---|
Use the time crate |
Duration and Calculation
Recipe | Crates | Categories |
---|---|---|
Measure the elapsed time between two code sections | ||
Perform checked date and time calculations | ||
Convert a local time to another timezone |
Measure the elapsed time between two code sections
Measures std::time::Instant::elapsed
⮳ since std::time::Instant::now
⮳
Calling std::time::Instant::elapsed
⮳ returns a std::time::Duration
⮳ that we print at the end of the example. This method will not mutate or reset the std::time::Instant
⮳ object.
use std::thread; use std::time::Duration; use std::time::Instant; fn expensive_function() { thread::sleep(Duration::from_secs(1)); } fn main() { let start = Instant::now(); expensive_function(); let duration = start.elapsed(); println!("Time elapsed in expensive_function() is: {:?}", duration); }
Perform checked date and time calculations
Calculates and displays the date and time two weeks from now using chrono::Date::checked_add_signed
⮳ and the date of the day before that using chrono::Date::checked_sub_signed
⮳
The methods return None if the date and time cannot be calculated.
Escape sequences that are available for the
chrono::DateTime::format
⮳ can be found at chrono::format::strftime
⮳.
use chrono::DateTime; use chrono::Duration; use chrono::TimeDelta; use chrono::Utc; fn day_earlier(date_time: DateTime<Utc>) -> Option<DateTime<Utc>> { date_time.checked_sub_signed(Duration::try_days(1).unwrap()) } fn main() { let now = Utc::now(); println!("{}", now); let almost_three_weeks_from_now = now .checked_add_signed(Duration::try_weeks(2).unwrap()) .and_then(|in_2weeks| { in_2weeks.checked_add_signed(Duration::try_weeks(1).unwrap()) }) .and_then(day_earlier); match almost_three_weeks_from_now { Some(x) => println!("{}", x), None => eprintln!("Almost three weeks from now overflows!"), } match now.checked_add_signed(TimeDelta::MAX) { 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." ), } }
Convert a local time to another timezone
Gets the local time and displays it using chrono::offset::Local::now
⮳ and then converts it to the UTC standard using the chrono::DateTime::from_utc
⮳ struct method. A time is then converted using the chrono::offset::FixedOffset
⮳ struct and the UTC time is then converted to UTC+8 and UTC-2.
use chrono::DateTime; use chrono::FixedOffset; use chrono::Local; use chrono::TimeZone; fn main() { let local_time = chrono::Local::now(); // Separate into components let utc_time = local_time.naive_utc(); let offset = *local_time.offset(); // Serialize, pass through FFI... and recreate the `DateTime`: let local_time_new = DateTime::<Local>::from_naive_utc_and_offset(utc_time, offset); assert_eq!(local_time, local_time_new); // there is also let _utc_time = chrono::Utc::now(); let china_timezone = FixedOffset::east_opt(8 * 3600).unwrap(); let rio_timezone = FixedOffset::west_opt(2 * 3600).unwrap(); println!("Local time now is {}", local_time); println!("UTC time now is {}", utc_time); println!( "Time in Hong Kong now is {}", china_timezone.from_utc_datetime(&utc_time) ); println!( "Time in Rio de Janeiro now is {}", rio_timezone.from_utc_datetime(&utc_time) ); }
Parsing and Displaying
Recipe | Crates | Categories |
---|---|---|
Examine the date and time | ||
Convert date to UNIX timestamp and vice versa | ||
Display formatted date and time | ||
Parse string into DateTime struct |
Examine the date and time
Gets the current UTC chrono::DateTime
⮳ and its hour/minute/second via chrono::Timelike
⮳ and its year/month/day/weekday via chrono::Datelike
⮳
use chrono::Datelike; use chrono::Timelike; use chrono::Utc; fn main() { let now = Utc::now(); let (is_pm, hour) = now.hour12(); println!( "The current UTC time is {:02}:{:02}:{:02} {}", hour, now.minute(), now.second(), if is_pm { "PM" } else { "AM" } ); println!( "And there have been {} seconds since midnight", now.num_seconds_from_midnight() ); let (is_common_era, year) = now.year_ce(); println!( "The current UTC date is {}-{:02}-{:02} {:?} ({})", year, now.month(), now.day(), now.weekday(), if is_common_era { "CE" } else { "BCE" } ); println!( "And the Common Era began {} days ago", now.num_days_from_ce() ); }
Convert date to UNIX timestamp and vice versa
Converts a date given by chrono::naive::NaiveDate::from_ymd
⮳ and chrono::naive::NaiveTime::from_hms
⮳ to UNIX time stamp⮳ using chrono::naive::NaiveDateTime::timestamp
⮳
Then it calculates what was the date after one billion seconds since January 1, 1970 0:00:00 UTC, using chrono::naive::NaiveDateTime::from_timestamp
⮳.
use chrono::DateTime; use chrono::NaiveDate; use chrono::NaiveDateTime; fn main() { let date_time: NaiveDateTime = NaiveDate::from_ymd_opt(2017, 11, 12) .unwrap() .and_hms_opt(17, 33, 44) .unwrap(); println!( "Number of seconds between 1970-01-01 00:00:00 and {} is {}.", date_time, date_time.and_utc().timestamp() ); let date_time_after_a_billion_seconds = DateTime::from_timestamp(1_000_000_000, 0).unwrap(); println!( "Date after a billion seconds since 1970-01-01 00:00:00 was {}.", date_time_after_a_billion_seconds ); }
Display formatted date and time
Gets and displays the current time in UTC using chrono::offset::Utc::now
⮳.
Formats the current time in the well-known RFC 2822 format⮳ using chrono::DateTime::to_rfc2822
⮳ and RFC 3339
⮳ using chrono::DateTime::to_rfc3339
⮳ and in a custom format using chrono::DateTime::format
⮳.
use chrono::DateTime; use chrono::Utc; fn main() { let now: DateTime<Utc> = Utc::now(); println!("UTC now is: {}", now); println!("UTC now in RFC 2822 is: {}", now.to_rfc2822()); println!("UTC now in RFC 3339 is: {}", now.to_rfc3339()); println!( "UTC now in a custom format is: {}", now.format("%a %b %e %T %Y") ); }
Parse string into DateTime
struct
Parses a chrono::DateTime
⮳ struct from strings representing the well-known
RFC 2822 format⮳ and RFC 3339 format⮳, and a custom format, using
chrono::DateTime::parse_from_rfc2822
⮳ chrono::DateTime::parse_from_rfc2822
⮳ and
chrono::DateTime::parse_from_str
⮳ respectively.
Escape sequences that are available for the chrono::DateTime::parse_from_str
⮳ can be found at chrono::format::strftime
⮳. Note that the chrono::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 chrono::naive::NaiveDate
⮳ chrono::naive::NaiveTime
⮳ and chrono::naive::NaiveDateTime
⮳.
use chrono::DateTime; use chrono::NaiveDate; use chrono::NaiveDateTime; use chrono::NaiveTime; use chrono::format::ParseError; fn main() -> Result<(), ParseError> { let rfc2822 = DateTime::parse_from_rfc2822("Tue, 1 Jul 2003 10:52:37 +0200")?; println!("{}", rfc2822); let rfc3339 = DateTime::parse_from_rfc3339("1996-12-19T16:39:57-08:00")?; println!("{}", rfc3339); let custom = DateTime::parse_from_str( "5.8.1994 8:00 am +0000", "%d.%m.%Y %H:%M %P %z", )?; println!("{}", custom); let time_only = NaiveTime::parse_from_str("23:56:04", "%H:%M:%S")?; println!("{}", time_only); let date_only = NaiveDate::parse_from_str("2015-09-05", "%Y-%m-%d")?; println!("{}", date_only); let no_timezone = NaiveDateTime::parse_from_str( "2015-09-05 23:56:04", "%Y-%m-%d %H:%M:%S", )?; println!("{}", no_timezone); Ok(()) }
Time
Recipe | Crates | Categories |
---|---|---|
Use the time crate |
Use the time
crate
Date and time library. Fully interoperable with the standard library. Mostly compatible with #![no_std]
.
fn main() { todo!(); }
Tools
Tools that provide developer-facing features such as testing, debugging, linting, performance profiling, autocompletion, formatting, and more.
Cargo
Recipe | Crates | Categories |
---|---|---|
Basic cargo usage | ||
Cargo.toml and lock files |
Recipe | Crates | Categories |
---|---|---|
Crate registries | crates.io⮳ |
Recipe | Crates | Categories |
---|---|---|
Package layout |
Documentation
Recipe | Crates | Categories |
---|---|---|
Document your code | ||
Create module- or crate-level documentation |
| mdbook-cmdrun
| | |
| Create a journal with mdbook-journal
| | |
| Check links with mdbook-linkcheck
| | |
| mdbook-pagetoc
| | |
| Hide entire chapters with mdbook-private
| | |
| Hide pages with mdbook-hide
| | |
| Create pages from a template with mdbook-tera
| | |
| mdbook-theme
| | |
| mdbook-toc
| | |
| Test code in your mdbook
| Byron-termbook | |
| Replace text in chapters with yapp
| | |
Formatting
Recipe | Crates | Categories |
---|---|---|
Format your Rust code with rustfmt | ||
Configure rustfmt | ||
Use attributes to skip code formatting in your code |
Installation
Recipe | Crates | Categories |
---|---|---|
Install and manage Rust toolchains with rustup |
Recipe | Crates | Categories |
---|---|---|
Build and install a Rust binary with cargo install | ||
Install a Rust binary with cargo binstall |
Other
Recipe | Crates | Categories |
---|---|---|
Save and run project-specific commands with the just command runner | ||
Check your Rust code in the background |
Recipe | Crates | Categories |
---|---|---|
Verify your Rust code | {{hi:kani}} |
Recipe | Crates | Categories |
---|---|---|
Install the miri interpreter | ||
Detect undefined behavior with the miri interpreter |
Recipe | Crates | Categories |
---|---|---|
Search for Rust APIs | roogle | |
Deploy your Rust code on shuttle.rs | shuttle.rs | |
Minimize Rust binary sizes | {{#crate }} | |
Generate Rust code | {{#crate }} |
Versioning
fd-find as a more human-friendly alternative to find which, by default, ignores paths listed in things like .gitignore and mimics Vim's smartcase option.
hyperfine as an analogue to the UNIX time command which can do warm-up runs, run the command multiple times to do statistical outlier detection, display a progress bar for the multiple runs, export results to CSV/JSON/etc., parameterize the runs, etc.
miniserve as a simple, easy way to serve up some files or accept some uploads over HTTP.
ripgrep for fast searching of file contents
rust-script as a way to quickly write little single-file programs in Rust without having to spin up a whole project.
skim as a Rust clone of fzf with some additional features. (Including being usable as a library you can embed in your own programs)
tokei for gathering statistics about a codebase (i.e. number of files, lines, lines of code, lines of comments, and lines of blanks, per language)
xd as an alternative to xxd that doesn't have as many features, but renders un-printable characters in a reversible "codepage 437 plus a symbol for NULL" mapping to ensure that all patterns in the visualization of binary files are visible... not just ones that occur in printable characters.
cross
Cargo, the Rust Package Manager
Recipe | Crates | Categories |
---|---|---|
Basic cargo usage | ||
Cargo.toml and lock files |
Basic cargo
usage
cargo help
or cargo <command> --help
cargo --version
# Create a new project. Can add --bin or --lib
cargo new hello_cargo
# Creates an executable file in target/debug/hello_cargo
cargo build
cargo build --release
# Build and run a project in one step
cargo run
# Pass arguments to the program and collect output
cargo run -- arg1 somefile.txt > output.txt
# Quickly checks your code to make sure it compiles but doesn’t produce an executable
cargo check
# Removes build artifacts
cargo clean
# Looks for tests to run in two places: in each of your src files and any tests in tests/.
cargo test
# Updates all dependencies - respect the SemVer constraints in cargo.toml
cargo update
# Updates just “regex”
cargo update -p regex
Cargo.toml
and lock files
# Configure the package
[package]
name = "hello_cargo"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at
# <https://doc.rust-lang.org/cargo/reference/manifest.html>
[dependencies]
# Reference a crate to be pulled from `crates.io`
time = "0.1.12"
# This is equivalent to the ^0.1.12 SemVer version range.
# `cargo update time` should update to version 0.1.13 if it is the latest 0.1.z release,
# but would not update to 0.2.0
# Reference a Git repo
regex = { git = "https://github.com/rust-lang/regex.git" }
# Reference a sub-crate
# Points to folder `hello_utils`, inside of which a `Cargo.toml` and `src` folder
hello_utils = { path = "hello_utils", version = "0.1.0" }
Examples of version requirements and the versions that would be allowed with them:
1.2.3 := >=1.2.3, <2.0.0
1.2 := >=1.2.0, <2.0.0
1 := >=1.0.0, <2.0.0
0.2.3 := >=0.2.3, <0.3.0
0.2 := >=0.2.0, <0.3.0
0.0.3 := >=0.0.3, <0.0.4
0.0 := >=0.0.0, <0.1.0
0 := >=0.0.0, <1.0.0
Details in Specifying Dependencies⮳
If you’re building a non-end product, such as a rust library that other rust packages will depend on, put Cargo.lock
in your .gitignore
.
If you’re building an end product, which are executable like command-line tool or an application, or a system library with crate-type of staticlib
⮳ or cdylib
⮳, check Cargo.lock
into git.
# Add dependencies to Cargo.toml from the command line
cargo add actix-web@4.0.0
Package layout
Recipe | Crates | Categories |
---|---|---|
Package layout |
.
├── Cargo.lock
├── Cargo.toml
├── src/
│ ├── lib.rs # The default library file is src/lib.rs.
│ ├── main.rs # The default executable file is src/main.rs.
│ └── bin/ # Other executables can be placed in src/bin/,
│ ├── named-executable.rs # even in library projects.
│ ├── another-executable.rs
│ └── multi-file-executable/
│ ├── main.rs
│ └── some_module.rs
├── benches/
│ ├── large-input.rs
│ └── multi-file-bench/
│ ├── main.rs
│ └── bench_module.rs
├── examples/
│ ├── simple.rs # cargo run --example simple
│ └── multi-file-example/
│ ├── main.rs
│ └── ex_module.rs
└── tests/ # Integration tests go in the tests directory.
├── some-integration-tests.rs # Tests in your src files should be unit tests
└── multi-file-test/ # and documentation tests.
├── main.rs
└── test_module.rs
If you’re building a non-end product, such as a rust library that other rust packages will depend on, put Cargo.lock
in your .gitignore
.
- A package is a bundle of one or more crates - as defined by a
Cargo.toml
file - A crate is the smallest amount of code that the Rust compiler considers at a time.
- A crate can come in one of two forms: a binary crate (must have a function called
main
⮳) or a library crate. - A package can contain as many binary crates as you like, but at most only one library crate.
- If a package contains
src/main.rs
andsrc/lib.rs
, it has two crates: a binary and a library, both with the same name as the package.
Crate Registries
Recipe | Crates | Categories |
---|---|---|
Crate registries | crates.io⮳ |
In Rust, a library or executable program is called a crate. Crates are compiled using the Rust compiler, rustc
⮳.
Crate registries
The Rust community’s crate registry: crates.io
⮳
Alternative to crates.io
: lib.rs
⮳
fn main() { todo!(); }
Installing
Recipe | Crates | Categories |
---|---|---|
Build and install a Rust binary with cargo install | ||
Install a Rust binary with cargo binstall |
Build and install a Rust binary with cargo install
This command manages cargo
’s local set of installed binary crates. Only packages which have executable [[bin]]
or [[example]]
targets can be installed, and all executables are installed into the installation root’s bin folder. By default only binaries, not examples, are installed. There are multiple sources from which a crate can be installed. The default source location is crates.io
, but the --git, --path, and --registry flags can change this source. This command operates on system or user level, not project level.
Install a Rust binary with cargo binstall
cargo binstall
provides a low-complexity mechanism for installing Rust binaries as an alternative to building from source (e.g. via cargo install
) or manually downloading packages. This is intended to work with existing CI artifacts and infrastructure, and with minimal overhead for package maintainers.
cargo binstall
works by fetching the crate information from crates.io
and searching the linked repository for matching releases and artifacts, falling back to the quickinstall
third-party artifact host, to alternate targets as supported, and finally to cargo install
as a last resort.
Rustup
Recipe | Crates | Categories |
---|---|---|
Install and manage Rust toolchains with rustup |
Install and manage Rust toolchains with rustup
rustup
⮳ is a toolchain multiplexer. It installs, manages, and upgrades versions of the rust compiler rustc
, the Rust package manager cargo
, the Rust linter clippy
, the Rust code formatter rustfmt
, etc.
More precisely, rustup
can install and manage multiple Rust toolchains and presents them all through a single set of tools installed to ~/.cargo/bin
. The rustc
⮳ and cargo
⮳ executables installed e.g. in ~/.cargo/bin
are proxies that delegate to the real toolchain.
rustup
is similar to Python's pyenv
⮳ or Node's nvm
⮳.
Key rustup
commands⮳ include the following:
# Rustup's help
rustup help
# Show the help page for a subcommand (like `toolchain`)
rustup toolchain help
# Show which toolchain will be used in the current directory
rustup show
# Update to a new version of Rust
rustup update
# Show which toolchain will be used in the current directory
rustup target list
# Overview of what is installed on your system
rustup toolchain list
# See a list of available and installed components.
rustup component list
Rustup also offers convenience commands:
# Open the local documentation in your browser
rustup doc
# May require `rustup component add rust-docs`
Text Editors
Applications for editing text.
Recipe | Crates | Categories |
---|---|---|
Write Rust code with VS Code | VS Code⮳ | |
Write Rust code with the zed editor |
| Use the Helix Editor | Helix editor⮳ | |
| Write Rust code with RustRover | RustRover
⮳ | |
| Use neovim
| Neovim⮳ | |
Formatting and Linting
Recipe | Crates | Categories |
---|---|---|
Format your Rust code with rustfmt | ||
Configure rustfmt | ||
Use attributes to skip code formatting in your code |
Format your Rust code with rustfmt
Install with rustup component add rustfmt
rustfmt <filename e.g. lib.rs> <main.rs> ...
# or for the whole project
cargo fmt
Using --check
instructs rustfmt
⮳ to exit with an error code if the input is not formatted correctly (useful for CI).
cargo fmt --all -- --check
Configure rustfmt
Create a rustfmt.toml
in the project root folder.
For example,
edition = "2021"
style_edition = "2021"
unstable_features = true
newline_style = "Unix"
#max_width = 100 # default: 100
use_small_heuristics = "Max"
format_code_in_doc_comments = true
indent_style = "Visual"
# Imports
imports_granularity = "Item" # or "Crate" or "Module"
imports_layout = "Vertical"
group_imports = "StdExternalCrate"
# Comments
comment_width = 100
wrap_comments = true
normalize_comments = true
normalize_doc_attributes = true
# Functions
fn_params_layout = "Compressed"
# Impl
reorder_impl_items = true
# Structs
use_field_init_shorthand = true
# Macros
use_try_shorthand = true
List config options with
rustfmt --help=config
Use attributes to skip code formatting in your code
For things you do not want rustfmt to mangle, use #[rustfmt::skip]
, #[rustfmt::skip::macros(macro_name)]
, or #![rustfmt::skip::attributes(custom_attribute)]
Documentation
Recipe | Crates | Categories |
---|---|---|
Document your code | ||
Create module- or crate-level documentation |
Document your code
/// This is a doc comment /// Note the three slashes /// The first line is equivalent to the next line. /// This is a doc comment fn documented_function() { println!("Function with doc comment."); } // Alternatively, you may use an external file #[doc = include_str!("../../../../README.md")] fn function_including_external_file_as_documentation() {} fn main() { documented_function(); function_including_external_file_as_documentation(); }
rustdoc
⮳ uses the CommonMark Markdown specification.
#![allow(dead_code)] /// Returns a person with the name given them /// /// # Arguments /// /// * `name` - A string slice that holds the name of the person /// /// # Examples /// /// ``` /// // You can have rust code between fences inside the comments /// // If you pass --test to `rustdoc`, it will even test it for you! /// use doc::Person; /// let person = Person::new("name"); /// ``` fn new(name: &str) -> Person { Person { name: name.to_string(), } } #[derive(Debug)] struct Person { name: String, } fn main() { let john = new("John"); println!("{:?}", john); }
Any item annotated with #[doc(hidden)]
will not appear in the documentation.
Run rustdoc src/lib.rs --crate-name <name>
or cargo doc --open
to create a new directory, doc
(or target/doc
when using cargo), with a website inside.
Create module- or crate-level documentation
Use //!
at the top of the file (instead of ///
) for module-level documentation.
The first lines within lib.rs
will compose the crate-level documentation front-page.
//! Fast and easy queue abstraction. //! //! Provides an abstraction over a queue. When the abstraction is used //! there are these advantages: //! - Fast //! - `[Easy]` //! //! [Easy]: http://thatwaseasy.example.com fn main() { println!( "//! ... are `inner` comments that apply to the containing module (or crate)." ); }
To add a "run" button on your documentation (allowing its execution in the rust playground), use the following attribute:
#![doc(html_playground_url = "https://playground.example.com/")] fn main() { println!("Note the above is an _inner_ attribute that starts with #!"); println!("It should be place at the top of your crate.") }
See also
docs.rs
⮳: open-source documentation host for Rust crates.
Add documentation to function arguments in Rust
Seamlessly document function parameters with rustdoc.
mdBook
| mdbook-cmdrun
| | |
| Create a journal with mdbook-journal
| | |
| Check links with mdbook-linkcheck
| | |
| mdbook-pagetoc
| | |
| Hide entire chapters with mdbook-private
| | |
| Hide pages with mdbook-hide
| | |
| Create pages from a template with mdbook-tera
| | |
| mdbook-theme
| | |
| mdbook-toc
| | |
| Test code in your mdbook
| Byron-termbook | |
| Replace text in chapters with yapp
| | |
Write online books with mdBook
mdBook⮳ is a utility to create modern online books from Markdown files.
cargo install mdbook
mdbook serve --open
Let readers execute your sample code in the Rust playground
Augment mdbook
with plugins
mdbook
has a large number of third-party plugins⮳.
Check links with mdbook-linkcheck
mdbook-linkcheck
is a backend for mdbook
which will check your links for you.
Hide entire chapters with mdbook-private
An mdbook preprocessor that controls visibility of private chapters and sections within them
Hide pages with mdbook-hide
A preprocessor for mdbook that adds support for hidden chapters
cargo install mdbook-hide
Deploy your book or documentation in a CD / CI pipeline
GitHub Actions is a continuous integration and continuous delivery (CI/CD) platform that allows you to automate your build, test, and deployment pipeline.
GitHub Actions for mdBook allows you to build your site with mdbook
. Linux (Ubuntu), macOS, and Windows are supported.
name: github pages
on:
push:
branches:
- main
pull_request:
jobs:
deploy:
runs-on: ubuntu-20.04
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
steps:
- uses: actions/checkout@v2
- name: Setup mdBook
uses: peaceiris/actions-mdbook@v2
with:
mdbook-version: '0.4.10'
# mdbook-version: 'latest'
- run: mdbook build
- name: Deploy
uses: peaceiris/actions-gh-pages@v3
if: ${{ github.ref == 'refs/heads/main' }}
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./book
Test code in your mdbook
Byron-termbook is a runner for mdbook
s to keep your documentation tested:
Add a table of contents to each page
mdbook-toc
A preprocessor for mdbook to add inline Table of Contents support.
mdbook-theme
A preprocessor and a backend to config theme for mdbook, especially creating a pagetoc on the right and setting full color themes from the offical ace editor.
mdbook-pagetoc
A mdbook plugin that provides a table of contents for each page.
Create pages from a template
Create pages from a template with mdbook-tera
mdbook-tera
is a Tera preprocessor for mdBook.
Replace text in chapters with yapp
yapp
is a mdbook preprocessor that simply replaces text in chapters. Phrases to be replaced with specified content are defined in plain-text configuration file.
Create a journal with mdbook-journal
Journal plugin for mdBook.
Other
mdbook-cmdrun
A mdbook
preprocessor to run arbitrary commands.
Badges
A badge is a small image that displays status information about a crate, for example the version number or build status, and links to a detailed page.
fn main() { todo!(); }
Versioning
Parse and increment a version string
Constructs a semver::Version
⮳ from a string literal using semver::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.
use anyhow::Result; use semver::Version; fn main() -> Result<()> { let parsed_version = Version::parse("0.2.6")?; // Note: a SemVer version must have exactly three components assert_eq!(parsed_version, Version { major: 0, minor: 2, patch: 6, pre: semver::Prerelease::EMPTY, build: semver::BuildMetadata::EMPTY, }); // 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(()) }
Parse a complex version string
Constructs a semver::Version
⮳ from a complex version string using semver::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.
use anyhow::Result; use semver::BuildMetadata; use semver::Prerelease; use semver::Version; fn main() -> Result<()> { let version_str = "1.0.49-125+g72ee7853"; let parsed_version = Version::parse(version_str)?; assert_eq!(parsed_version, Version { major: 1, minor: 0, patch: 49, pre: Prerelease::new("125")?, build: BuildMetadata::new("g72ee7853")?, }); let serialized_version = parsed_version.to_string(); assert_eq!(&serialized_version, version_str); println!("{}", serialized_version); Ok(()) }
Check if a given version is pre-release
Given two versions, semver::Version
⮳ asserts that one is pre-release and the other is not.
use anyhow::Result; use semver::Version; fn main() -> Result<()> { let version_1 = Version::parse("1.0.0-alpha")?; println!("{:?}", version_1); let version_2 = Version::parse("1.0.0")?; println!("{:?}", version_2); assert!(!version_1.pre.is_empty()); assert!(version_2.pre.is_empty()); Ok(()) }
Find the latest version satisfying a given range
Given a list of version &strs, finds the latest semver::Version
⮳
semver::VersionReq
⮳ filters the list with semver::VersionReq::matches
⮳ Also demonstrates semver
⮳ pre-release preferences.
use anyhow::Result; use semver::Version; use semver::VersionReq; fn find_max_matching_version<'a, I>( version_req_str: &str, iterable: I, ) -> Result<Option<Version>> where I: IntoIterator<Item = &'a str>, { let vreq = VersionReq::parse(version_req_str)?; Ok(iterable .into_iter() .filter_map(|s| Version::parse(s).ok()) .filter(|s| vreq.matches(s)) .max()) } fn main() -> Result<()> { assert_eq!( find_max_matching_version("<= 1.0.0", vec!["0.9.0", "1.0.0", "1.0.1"])?, Some(Version::parse("1.0.0")?) ); 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(()) }
Check external command version for compatibility
Runs git --version
using std::process::Command
⮳ then parses the version number into a
semver::Version
⮳ using semver::Version::parse
⮳ semver::VersionReq::matches
⮳ compares
semver::VersionReq
to the parsed version. The command output resembles "git version x.y.z".
use std::process::Command; use anyhow::Context; use anyhow::Result; use anyhow::anyhow; use anyhow::bail; use semver::Version; use semver::VersionReq; fn main() -> Result<()> { let version_constraint = "> 1.12.0"; let version_test = VersionReq::parse(version_constraint)?; let output = Command::new("git").arg("--version").output()?; if !output.status.success() { bail!("Command executed with failing error code"); } let stdout = String::from_utf8(output.stdout)?; let version = stdout .split(" ") .last() .ok_or_else(|| anyhow!("Invalid command output"))? .trim(); // Remove any extraneous newlines let parsed_version = Version::parse(version).context(format!("version: {}", version))?; if !version_test.matches(&parsed_version) { bail!( "Command version lower than minimum supported version (found {}, need {})", parsed_version, version_constraint ); } println!("{:?}", parsed_version); Ok(()) }
Others
Recipe | Crates | Categories |
---|---|---|
Search for Rust APIs | roogle | |
Deploy your Rust code on shuttle.rs | shuttle.rs | |
Minimize Rust binary sizes | {{#crate }} | |
Generate Rust code | {{#crate }} |
Search for Rust APIs
Roogle is a Rust API search engine, which allows you to search functions by names and type signatures. The query can be one of the following types:
fn f(type) -> type | fn f(&mut HashMap<K, V>, K, V) -> Option |
fn (type) -> type | fn (&char) -> bool |
fn(type) -> type | fn(Option<Option |
(type) -> type | (&mut Vec |
Deploy your Rust code on shuttle.rs
fn main() { todo!(); }
Minimize Rust binary sizes
How to minimize Rust binary size⮳
By default, Rust optimizes for execution speed, compilation speed, and ease of debugging. This approach is suitable for most applications, as it balances performance and developer productivity. However, in specific scenarios where binary size is a critical concern (e.g., embedded systems or deployment to constrained environments), Rust offers mechanisms to optimize for smaller binary sizes.
cargo build --release
Generate Rust code
Top artificial intelligence tools that can generate code to help programmers⮳
Manage and build code
Recipe | Crates | Categories |
---|---|---|
Save and run project-specific commands with the just command runner | ||
Check your Rust code in the background |
Save and run project-specific commands with the just
command runner
just
is a command runner. It is a Rust-based equivalent to make
without the ability to detect file changes, but with significantly fewer syntactic warts.
Consult the Just programmer's manual⮳.
Create a justfile
Place the following example justfile
in the root folder of your project. Run just
to see a list of recipes. Run just <recipe>
to execute the corresponding recipe.
# Load a .env file, if present.
set dotenv-load
default:
@just --list --unsorted
# Check a local package and all of its dependencies for errors
check:
@cargo check
# Compile a local package and all of its dependencies
build: check
@cargo build
# Run a binary or example of the local packagels
run: check
@cargo run
system-info:
@echo "This is an {{arch()}} machine".
# Shebang script example
foo:
#!/usr/bin/env bash
set -euxo pipefail
hello='Yo'
echo "$hello from Bash!"
Install just
in a Dev Container
FROM mcr.microsoft.com/devcontainers/base:bullseye
# or perhaps mcr.microsoft.com/devcontainers/rust:bullseye if you want rust & cargo
SHELL ["bash", "-c"]
# Prerequisites to install Just: https://just.systems/man/en
RUN <<EOF
wget -qO - 'https://proget.makedeb.org/debian-feeds/prebuilt-mpr.pub' | gpg --dearmor | sudo tee /usr/share/keyrings/prebuilt-mpr-archive-keyring.gpg 1> /dev/null
echo "deb [arch=all,$(dpkg --print-architecture) signed-by=/usr/share/keyrings/prebuilt-mpr-archive-keyring.gpg] https://proget.makedeb.org prebuilt-mpr $(lsb_release -cs)" | sudo tee /etc/apt/sources.list.d/prebuilt-mpr.list
sudo apt update
EOF
RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
&& apt-get -y install just \
&& apt-get autoremove -y && apt-get clean -y
For Alpine, use apk
⮳:
RUN apk add just
Check your Rust code in the background
bacon
is a background rust code checker. It is designed for minimal interaction, so that you can just let it run, alongside your editor, and be notified of warnings, errors, or test failures in your Rust code.
# Install or update `bacon`
cargo install --locked bacon
# Check the current project
bacon
# Run clippy instead of cargo check
bacon clippy
Code Verification and Formal Methods
Recipe | Crates | Categories |
---|---|---|
Verify your Rust code | {{hi:kani}} |
Formal Methods are a collection of mathematically rigorous techniques used to specify, design, and verify software and hardware systems. These methods employ formal logic and mathematical proofs to ensure the correctness and reliability of systems, especially those that are safety-critical or security-critical.
Program verification is the process of formally proving the correctness of a program. This involves analyzing the code and ensuring that it meets specific properties, such as:
- Memory safety: The program does not have memory leaks, buffer overflows, or other memory-related errors.
- Thread safety: The program can be executed concurrently without causing data races or other concurrency issues.
- Functional correctness: The program produces the correct output for all valid inputs.
- Performance: The program meets specific performance requirements, such as execution time or memory usage.
There are two main approaches to Rust program verification:
-
Static verification: This involves analyzing the code at compile time to identify potential errors and prove the correctness of certain properties. Rust's type system and ownership model already provide a strong foundation for static verification. Additionally, tools like
miri
andkani
can be used to perform more advanced static analysis. -
Dynamic verification: This involves running the program with different inputs and checking its behavior against expected results. Techniques like fuzz testing can be used to identify potential issues.
Verify your Rust code
kani⮳ is a Rust verifier.
fn main() { todo!(); }
Miri Interpreter
Recipe | Crates | Categories |
---|---|---|
Install the miri interpreter | ||
Detect undefined behavior with the miri interpreter |
Detect undefined behavior with the miri
interpreter
miri
⮳ is an experimental interpreter for Rust's mid-level intermediate representation (MIR). It can run binaries and test suites of cargo projects and detect certain classes of undefined behavior. It can run binaries and test suites of cargo projects and detect unsafe code that fails to uphold its safety requirements. It can also perform cross-interpretation for arbitrary foreign targets.
Install the miri
interpreter
rustup +nightly component add miri
cargo clean
cargo miri test
# or
cargo miri run
Build Time Tooling
Utilities for build scripts and other build time steps.
Recipe | Crates | Categories |
---|---|---|
Compile and link statically to a bundled C library | ||
Compile and link statically to a bundled C++ library | ||
Compile a C library while setting custom defines |
Use sccache
Sccache is a ccache-like tool. It is used as a compiler wrapper and avoids compilation when possible. Sccache has the capability to utilize caching in remote storage environments, including various cloud storage options, or alternatively, in local storage.
Build time tooling
Recipe | Crates | Categories |
---|---|---|
Compile and link statically to a bundled C library | ||
Compile and link statically to a bundled C++ library | ||
Compile a C library while setting custom defines |
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.
Compile and link statically to a bundled C library
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 cc::Build::compile
⮳) which can then be used from rust by declaring the external function signatures in an compile
⮳, which can then be used from rust by declaring the external function signatures in an extern block⮳ 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 cc::Build::include
⮳ paths and extra compiler cc::Build::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");
}
void greet(const char* name) {
printf("Hello, %s!\n", name);
}
src/main.rs
use std::ffi::CString; use std::os::raw::c_char; use anyhow::Result; 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()) } unsafe extern "C" { fn hello(); fn greet(name: *const c_char); } fn main() -> Result<()> { // unsafe { hello() } let name = prompt("What's your name? ")?; let c_name = CString::new(name)?; // unsafe { greet(c_name.as_ptr()) } Ok(()) }
Compile and link statically to a bundled C++ library
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 cc::Build::cpp
⮳ and preventing name mangling by the C++ compiler by adding the extern "C"
section at the top of our C++ source file.
Cargo.toml
(static C++)
[package]
...
build = "build.rs"
[build-dependencies]
cc = "1"
build.rs
(static C++)
fn main() { // cc::Build::new() // .cpp(true) // .file("src/foo.cpp") // .compile("foo"); // println!(""); }
src/foo.cpp
(static C++)
extern "C" {
int multiply(int x, int y);
}
int multiply(int x, int y) {
return x*y;
}
src/main.rs
(static C++)
unsafe extern "C" { fn multiply(x: i32, y: i32) -> i32; } fn main() { // unsafe { // println!("{}", multiply(5, 7)); // } }
Compile a C library while setting custom defines
It is simple to build bundled C code with custom defines using cc::Build::define
⮳
The method takes an std::option::Option
⮳ value, so it is possible to create defines such as #define APP_NAME "foo"
as well as #define WELCOME
(pass std::option::Option::None
⮳ as the value for a value-less define). This example builds
a bundled C file with dynamic defines set in build.rs
and prints "Welcome to foo - version 1.0.2
"
when run. Cargo sets some environment variables⮳ which may be useful for some custom defines.
Cargo.toml
(custom defines)
[package]
...
version = "1.0.2"
build = "build.rs"
[build-dependencies]
cc = "1"
build.rs
(custom defines)
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
(custom defines)
#include <stdio.h>
void print_app_info() {
#ifdef WELCOME
printf("Welcome to ");
#endif
printf("%s - version %s\n", APP_NAME, VERSION);
}
src/main.rs
(custom defines)
unsafe extern "C" { fn print_app_info(); } fn main() { // unsafe { // print_app_info(); // } println!("Printed app info."); }
Cargo Plugins
Subcommands that extend the capabilities of Cargo.
Writing code
Recipe | Crates | Categories |
---|---|---|
Generate a Rust project from a template | ||
Quickly open the crates.io or docs.rs page for the latest version of a crate |
Formatting and Linting
Recipe | Crates | Categories |
---|---|---|
Format your code | ||
Lint your code | ||
Fix compiler warnings automatically | ||
Format or lint your code before committing it |
Building
Recipe | Crates | Categories |
---|---|---|
cargo make | ||
Use devx | ||
Make Rust a better bash with xshell |
Watching for changes
Recipe | Crates | Categories |
---|---|---|
cargo watch | ||
cargo limit |
Cross-compiling
Recipe | Crates | Categories |
---|---|---|
Cross-compile using zig as the linker |
Auditing
Performance
Recipe | Crates | Categories |
---|---|---|
Configure your cargo project for maximum performance, fast compile times or minimal binary size | ||
cargo hakari |
Maintenance
Recipe | Crates | Categories |
---|---|---|
Edit Cargo.toml | ||
Find unused dependencies | ||
Detect dependencies that are out of date | ||
Lint your crate API changes for semver violations | ||
Manage the cargo cache |
| cargo expand
| | |
| cargo hack
| | |
See also
- Testing
- Build Utils
- Debugging
- FFI
- Procedural Macro Helpers
- Profiling
cargo-afl for fuzzing
cargo-asm and cargo-expand to investigate what the compiler generates from your code (cargo-expand shows the expanded output from macros)
cargo-audit for checking whether any of your dependencies are of a version that has a security advisory out against them.
cargo-bloat for identifying what's contributing to your binary's size (eg. modules with generic functions or macros not designed with size-efficiency in mind)
cargo-cache, cargo-sweep, and cargo-prune for keeping disk consumption by build artifacts and other regeneratable files under control.
cargo-deadlinks Check cargo doc output for broken old-style/manual intra-doc links.
cargo-edit for cargo add
cargo-geiger for identifying dependencies with unsafe code so you can either audit them or find alternatives if you don't feel skilled enough to do your own auditing.
cargo-modules for rendering a tree or Graphviz graph of the modules within a crate
cargo-outdated for listing packages that have newer versions than what your Cargo.toml and Cargo.lock are pinning.
cargo-tree to investigate dependencies (I like to use -d to list crates where more than one version is getting pulled in and what's pulling each version in.)
cargo-update to provide a cargo install-update to check for and install new versions of cargo install'd commands.
cargo-watch to re-run a command every time the source changes. (eg. cargo test)
flamegraph as an easy way to generate flamegraphs for visualizing performance profiles of Rust programs.
cargo-about, cargo-deny, cargo-license, or cargo-lichking for license compliance management
cargo-audit and cargo-sweep
cargo-spellcheck
Write code
Recipe | Crates | Categories |
---|---|---|
Generate a Rust project from a template | ||
Quickly open the crates.io or docs.rs page for the latest version of a crate |
Generate a Rust project from a template
Cargo Generate⮳ is a developer tool to help you get up and running quickly with a new Rust project by leveraging a pre-existing git repository as a template.
Quickly open the crates.io
or docs.rs
page for the latest version of a crate
cargo crates
is a cargo command to quickly open the crates.io
or docs.rs
page for the latest version of a crate.
Code formatting and linting
Recipe | Crates | Categories |
---|---|---|
Format your code | ||
Lint your code | ||
Fix compiler warnings automatically | ||
Format or lint your code before committing it |
Format your code
# Install `rustfmt` if needed
rustup component add rustfmt
cargo fmt
# Fails if code is not formatted; use in CD / CI
cargo fmt -- --check
Lint your code
Clippy is the official Rust linter. It catches common mistakes and improves your Rust code.
rustup component add clippy # install if needed
cargo clippy
Mute a warning using the #[allow(clippy::lint_name)]
attributes.
Fix compiler warnings automatically
Can automatically fix compiler warnings that have a clear way to correct the problem that’s likely what you want.
cargo fix
Format or lint your code before committing it
cargo-husky⮳ setup Git hooks automatically for cargo projects with 🐶
Git hook scripts are useful for identifying simple issues (failing tests, trailing white spaces, formatting of the code, of JSON, and YAML files...) before committing code, prior to submission to code review.
Add the cargo-husky
crate to the [dev-dependencies]
section of your project's Cargo.toml
.
[dev-dependencies]
cargo-husky = "1"
Then run tests in your project directory.
cargo test
See also pre-commit
⮳, which is a Python framework for managing and maintaining multi-language pre-commit hooks.
Build and run
Recipe | Crates | Categories |
---|---|---|
cargo make | ||
Use devx | ||
Make Rust a better bash with xshell |
cargo make
Rust task runner and build tool. The cargo-make task runner enables to define and configure sets of tasks and run them as a flow. A task is a command, script, rust code, or other sub tasks to execute. Tasks can have dependencies which are also tasks that will be executed before the task itself. With a simple toml based configuration file, you can define a multi platform build script that can run build, test, generate documentation, run bench tests, run security validations and more, executed by running a single command.
Install with
cargo install --force cargo-make
cargo make --version
automating-your-rust-workflows-with-cargo-make⮳
cargo xtask
cargo-xtask⮳ adds free-form automation to a Rust project, a-la make
, npm run
or bespoke bash scripts.
The two distinguishing features of xtask
are the following:
- It doesn't require any other binaries besides
cargo
andrustc
, it fully bootstraps from them - Unlike bash, it can more easily be cross platform, as it doesn't use the shell.
Use devx
devx
⮳ is a collection of utilities for writing your own dev scripts in Rust. The project is inspired by and intended for seamless usage with cargo-xtask
⮳ idioms.
devx-cmd
provides primitives for spawning child processes that are easier than std::process targeted
when used in development scripts. devx-pre-commit
creates git pre-commit hooks that enforce good practices.
Make Rust a better bash
with xshell
xshell
⮳ provides a set of cross-platform utilities for writing cross-platform and ergonomic "bash" scripts.
Cross-compilation
Recipe | Crates | Categories |
---|---|---|
Cross-compile using zig as the linker |
Cross-compile using zig
as the linker
Compile Cargo project with zig as linker.
cargo install --locked cargo-zigbuild
Watching for changes
Recipe | Crates | Categories |
---|---|---|
cargo watch | ||
cargo limit |
cargo watch
cargo install cargo-watch
# Runs `cargo check` after every code change
cargo watch -x check
# Run cargo check after code changes.
# If it succeeds, it launches cargo test.
# If tests pass, it launches the application with cargo run.
cargo watch -x check -x test -x run
cargo limit
cargo-limit⮳ is Cargo with less noise: warnings are skipped until errors are fixed, Neovim integration, etc.
- errors have highest priority
- they never appear in the middle of warnings
- warnings are skipped by default until errors are fixed
- external path dependencies' warnings are skipped by default
- all messages come in reversed order by default to avoid extra scrolling
- messages are grouped by filenames
- number of messages can be limited
- after encountering first error the rest of build time is limited by default
- files can be automatically opened in your text editor on affected lines
This tool is especially useful in combination with cargo-watch
.
Performance
Recipe | Crates | Categories |
---|---|---|
Configure your cargo project for maximum performance, fast compile times or minimal binary size | ||
cargo hakari |
Configure your cargo
project for maximum performance, fast compile times or minimal binary size
cargo wizard
⮳ is a cargo
subcommand for configuring Cargo projects. It applies profile and config templates to your Cargo project to configure it for maximum performance, fast compile times or minimal binary size.
cargo hakari
cargo-hakari⮳ manage "workspace-hack" packages to speed up builds in large workspaces.
cargo hakari
is a command-line application to manage "workspace-hack" crates. Use it to speed up local cargo build
and cargo check
commands by up to 100x, and cumulatively by up to 1.7x or more.
Audit
Audit cargo.lock
files for crates containing security vulnerabilities
cargo install cargo-audit
cargo audit
Embded the exact crate versions in your Rust executable for auditability
cargo-auditable⮳ makes production Rust binaries auditable.
It audits binaries for known bugs or security vulnerabilities in production, at scale, with zero bookkeeping.
This works by embedding data about the dependency tree in JSON format into a dedicated linker section of the compiled executable.
List the license(s) of dependencies
Cargo subcommand to see license of dependencies.
You can install cargo-license with cargo install cargo-license
and run it in your project directory with: cargo license
or cargo-license
.
cargo deny
cargo-deny
is a cargo plugin that lets you lint your project's dependency graph to ensure all your dependencies conform to your expectations and requirements.
- Checks the license information for each crate.
- Checks for / bans specific crates in your graph, as well as duplicates.
- Checks advisory databases for crates with security vulnerabilities, or that have been marked as Unmaintained, or which have been yanked from their source registry.
- Checks the source location for each crate.
Install with:
cargo install --locked cargo-deny
# Or, if you're an Arch user
pacman -S cargo-deny
cargo deny init
cargo deny check
cargo deny check licenses
Maintain
Recipe | Crates | Categories |
---|---|---|
Edit Cargo.toml | ||
Find unused dependencies | ||
Detect dependencies that are out of date | ||
Lint your crate API changes for semver violations | ||
Manage the cargo cache |
| cargo expand
| | |
| cargo hack
| | |
Edit Cargo.toml
cargo edit
provides commands for modifying a Cargo.toml
file. It allows you to add, remove, and upgrade dependencies by modifying your Cargo.toml
file from the command line.
Currently available subcommands:
cargo upgrade
cargo set-version
Find unused dependencies
cargo udeps
udeps⮳ find unused dependencies in Cargo.toml
.
While compilation of this tool also works on Rust stable, it needs Rust nightly to actually run.
cargo machete
cargo-machete
⮳ is a Cargo tool that detects unused dependencies in Rust projects, in a fast (yet imprecise) way.
Install and run with:
cargo install cargo-machete
cargo machete
Detect dependencies that are out of date
Cargo subcommand for displaying when dependencies are out of date.
If you are using VS Code, also look into the Dependi
VS Code plugin.
Lint your crate API changes for semver violations
cargo-semver-checks
scans your Rust crate for semver violations.
# If you already use `cargo-binstall` for faster tool installations:
$ cargo binstall cargo-semver-checks
# Otherwise:
$ cargo install cargo-semver-checks --locked
# Lint a new release for SemVer breakage before `cargo publish`:
$ cargo semver-checks
Manage the cargo
cache
cargo cache
⮳ manages the cargo
cache ($CARGO_HOME or ~/.cargo/), shows sizes and removes directories selectively.
cargo expand
Wrapper around rustc -Zunpretty=expanded
. Shows the result of macro expansion and #[derive]
expansion.
cargo hack
Cargo subcommand to provide various options useful for testing and continuous integration.
Debugging, Logging
Help you figure out what is going on with your code such as logging, tracing, or assertions.
Tracing
Recipe | Crates | Categories |
---|---|---|
Initialize the logger | ||
Enable basic tracing | ||
Combine layers | ||
Configure a custom event formatter | ||
Events | ||
Spans | ||
Add tracing spans to functions | ||
See also |
Logging
Recipe | Crates | Categories |
---|---|---|
Log a debug message to the console | ||
Log an error message to the console | ||
Log to stdout instead of stderr | ||
Log messages with a custom logger | ||
Log to the Unix syslog |
Log Configuration
Recipe | Crates | Categories |
---|---|---|
Enable log levels per module | ||
Use a custom environment variable to set up logging | ||
Include a timestamp in log messages | ||
Log messages to a custom location |
Alternatives
Recipe | Crates | Categories |
---|---|---|
log | ||
slog | ||
log4rs | ||
env_logger | ||
OpenTelemetry | OpenTelemetry Rust documentation⮳ | |
OpenObserve | OpenObserve⮳ |
Diagnostic functions
Recipe | Crates | Categories |
---|---|---|
Get the type name of the pointed-to value |
Logs
Recipe | Crates | Categories |
---|---|---|
Initialize the logger | ||
Enable basic tracing | ||
Combine layers | ||
Configure a custom event formatter | ||
Events | ||
Spans | ||
Add tracing spans to functions | ||
See also |
Add to Cargo.toml
[dependencies]
tracing = "0.1"
tracing-subscriber = "0.3"
Initialize the logger
Enable basic tracing
fn main() {
// Filter events at runtime using the value
// of the RUST_LOG environment variable:
// for example, RUST_LOG=debug,my_crate=trace
tracing_subscriber::fmt::init();
// This is equivalent to:
// tracing_subscriber::fmt()
// .with_env_filter(EnvFilter::from_default_env())
// .init();
tracing::info!("tracing configured!");
println!("Done.")
}
Combine layers
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::util::SubscriberInitExt;
fn main() {
tracing_subscriber::registry()
.with(tracing_subscriber::fmt::layer())
.with(tracing_subscriber::filter::EnvFilter::new(
std::env::var("RUST_LOG").unwrap_or_else(|_| {
"myproj=debug,axum=debug,tower_http=debug,mongodb=debug".into()
}),
))
.init();
tracing::info!("tracing configured!");
println!("Done.")
}
Or with a custom formatting layer
use tracing_subscriber::filter::EnvFilter;
use tracing_subscriber::fmt;
use tracing_subscriber::prelude::*;
fn main() {
let fmt_layer = fmt::layer().with_target(false);
let filter_layer = EnvFilter::try_from_default_env()
.or_else(|_| EnvFilter::try_new("info"))
.unwrap();
tracing_subscriber::registry()
.with(filter_layer)
.with(fmt_layer)
.init();
}
Configure a custom event formatter
use tracing_subscriber::fmt;
fn main() {
// Configure a custom event formatter
let format = fmt::format()
.with_level(false) // Don't include levels in formatted output
.with_target(false) // Don't include targets
.with_thread_ids(true) // Include the thread ID of the current thread
.with_thread_names(true) // Include the name of the current thread
.compact(); // Use the `Compact` formatting style.
// Create a `fmt` subscriber that uses our custom event format, and
// set it as the default.
tracing_subscriber::fmt().event_format(format).init();
}
Events
use tracing::Level; use tracing::debug; use tracing::error; use tracing::event; use tracing::info; use tracing::trace; use tracing::warn; #[derive(Debug)] struct S; fn main() { event!(Level::INFO, "something happened"); error!("error!"); warn!("warning!"); info!("info!"); debug!("debug info!"); trace!("trace info!"); event!(target: "app_events", Level::TRACE, "something has happened!"); // Records an event with two fields (also works for spans) event!( Level::INFO, answer = 42, question = "life, the universe, and everything" ); // Unlike other fields, `message`'s shorthand initialization is just // the string itself. debug!(excitement = "yay!", "hello!"); // Shorthand for `user = user` let user = "ferris"; event!(Level::TRACE, "login: {}", user); // Note the `?`: `my_struct` will be recorded // using its `fmt::Debug` implementation. let my_struct = S; event!(Level::TRACE, greeting = ?my_struct); }
Spans
use tracing::Level; use tracing::span; fn main() { let span = span!(Level::TRACE, "my_span"); { // Current lexical scope. let _guard = span.enter(); println!( "`enter` returns a RAII guard, which, when dropped, exits the span." ); println!("Any trace events that occur here will occur within the span.") } println!("Dropping the guard exits the span."); }
One-liner with .entered()
:
use tracing::Level; use tracing::span; fn main() { let span = span!(Level::TRACE, "some span").entered(); println!("Code here is within the span"); // optionally, explicitly exit the span, returning it let span = span.exit(); println!("Code here is no longer within the span"); // enter the span again let _span = span.entered(); println!("Code here is within the span"); }
Holding the drop guard returned by Span::enter
across .await
points will result in incorrect traces. Use tracing::span::Span::in_scope
⮳.
use tracing::Instrument; use tracing::debug_span; use tracing::info_span; async fn my_async_function() { let span = info_span!("my_async_function"); // Instrument synchronous code within an async functiom let _some_value = span.in_scope(|| { // Run some synchronous code inside the span... 42 }); // This is okay! // The span has already been exited before we reach the await point. some_other_async_function().await; // Instrument async code async move { // This is correct! If we yield here, the span will be exited, // and re-entered when we resume. some_other_async_function().await; } .instrument(span) // instrument the async block with the span... .await; // ...and await it. let _some_value = some_other_async_function() .instrument(debug_span!("some_other_async_function")) .await; } async fn some_other_async_function() {} #[tokio::main] async fn main() { my_async_function().await; }
Add tracing spans to functions
use tracing::Level; use tracing::event; use tracing::instrument; #[instrument] fn my_function(my_arg: usize) { // This event will be recorded inside a span named `my_function` // with the field `my_arg`. event!(Level::INFO, "inside my_function!"); // ... } // Used on an async function #[instrument(level = "info")] async fn my_async_function() { // This is correct! If we yield here, the span will be exited, // and re-entered when we resume. some_other_async_function().await; } async fn some_other_async_function() {} #[tokio::main] async fn main() { my_function(42); my_async_function().await; }
See also
tracing_journald⮳ provides support for logging tracing
⮳ events natively to journald⮳, preserving any structured information.
Log Messages
Recipe | Crates | Categories |
---|---|---|
Log a debug message to the console | ||
Log an error message to the console | ||
Log to stdout instead of stderr | ||
Log messages with a custom logger | ||
Log to the Unix syslog |
Log a debug message to the console
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 is simple logger that can be configured via environment // variables. Example: RUST_LOG=info ./app env_logger::init(); execute_query("DROP TABLE students"); }
No output prints when running this code. By default, the log level is error
, and any lower levels are dropped.
Set the RUST_LOG
⮳ environment variable to print the message:
RUST_LOG=debug cargo run
Cargo prints debugging information then the following line at the very end of the output:
DEBUG:main: Executing query: DROP TABLE students
Log an error message to the console
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(); let response = execute_query("DROP TABLE students"); if let Err(err) = response { log::error!("Failed to execute query: {}", err); } }
Log to stdout
instead of stderr
Creates a custom logger configuration using the env_logger::Builder::target
⮳ to set the target of the log output to env_logger::fmt::Target
⮳
use env_logger::Target; fn main() { env_logger::Builder::new().target(Target::Stdout).init(); log::error!("This error has been printed to Stdout"); }
Log messages with a custom logger
Implements a custom logger ConsoleLogger
which prints to stdout. In order to use the logging macros, ConsoleLogger
implements the log::Log
⮳ trait and log::Log
⮳ installs it.
use log::Level; use log::LevelFilter; use log::Metadata; use log::Record; use log::SetLoggerError; static CONSOLE_LOGGER: ConsoleLogger = ConsoleLogger; struct ConsoleLogger; impl log::Log for ConsoleLogger { fn enabled(&self, metadata: &Metadata) -> bool { metadata.level() <= Level::Info } fn log(&self, record: &Record) { if self.enabled(record.metadata()) { println!("Rust says: {} - {}", record.level(), record.args()); } } fn flush(&self) {} } fn main() -> Result<(), SetLoggerError> { log::set_logger(&CONSOLE_LOGGER)?; log::set_max_level(LevelFilter::Info); log::info!("hello log"); log::warn!("warning"); log::error!("oops"); Ok(()) }
Log to the Unix syslog
Logs messages to UNIX syslog
⮳. Initializes logger backend with syslog::init
⮳ syslog::init
⮳ records the program submitting the log entry's classification, syslog::init
⮳ denotes allowed log verbosity and Option<&str>
holds optional application name.
#[cfg(target_os = "linux")] fn main() -> anyhow::Result<()> { use syslog::Facility; syslog::init( Facility::LOG_USER, log::LevelFilter::Debug, Some("My app name"), )?; log::debug!("this is a debug {}", "message"); log::error!("this is an error!"); Ok(()) } #[cfg(not(target_os = "linux"))] fn main() -> anyhow::Result<()> { println!("So far, only Linux systems are supported."); Ok(()) }
Configure Logging
Recipe | Crates | Categories |
---|---|---|
Enable log levels per module | ||
Use a custom environment variable to set up logging | ||
Include a timestamp in log messages | ||
Log messages to a custom location |
Enable log levels per module
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(); }
The 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
.
WARN:test: [root] warn
WARN:test::foo: [foo] warn
INFO:test::foo: [foo] info
WARN:test::foo::bar: [bar] warn
INFO:test::foo::bar: [bar] info
DEBUG:test::foo::bar: [bar] debug
Use a custom environment variable to set up logging
env_logger::Builder
⮳ configures logging.
env_logger::Builder::parse
⮳ parses MY_APP_LOG
environment variable contents in the form of RUST_LOG
⮳ syntax.
Then, env_logger::Builder::init
⮳ initializes the logger.
All these steps are normally done internally by env_logger::init
⮳.
fn main() { init_logger(); log::info!("informational message"); log::warn!("warning message"); log::error!("this is an error {}", "message"); if log::log_enabled!(log::Level::Info) { let x = 3 * 4; // "Expensive" computation log::trace!("the answer was: {}", x); } } //#[cfg(not(test))] fn init_logger() { // env_logger is a simple logger that can be configured via environment // variables. Example: RUST_LOG=info ./app // Typically you would use: // env_logger::init(); // Initialise a logger with filter level Off, // then override the log filter from an environment variable called // MY_APP_LOG: env_logger::Builder::new() .filter_level(log::LevelFilter::Off) .parse_env("MY_APP_LOG") .init(); // Alternatively, `Env` lets us tweak what the environment // variables to read are and what the default // value is if they're missing // let env = env_logger::Env::default() // // Specify an environment variable to read the filter from. // // If the variable is not set, the default value will be used. // .filter_or("MY_APP_LOG", "trace") // .write_style_or("MY_APP_LOG_STYLE", "always"); // env_logger::init_from_env(env); }
Include a timestamp in log messages
Creates a custom logger configuration with env_logger::Builder
⮳
Each log entry calls chrono::offset::Local::now
⮳ to get the current chrono::DateTime
⮳ in local timezone and uses chrono::DateTime::format
⮳ with chrono::format::strftime
⮳ to format a timestamp used in the final log.
The example calls env_logger::Builder::format
⮳ to set a closure which formats each message text with timestamp, log::Record::level
⮳ and body (log::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"); }
stderr output will contain
2017-05-22T21:57:06 [WARN] - warn
2017-05-22T21:57:06 [INFO] - info
Log messages to a custom location
log4rs
⮳ configures log output to a custom location. log4rs
⮳ can use either an external YAML file or a builder configuration.
Create the log configuration with log4rs::append::file::FileAppender
⮳ An appender defines the logging destination. The configuration continues with encoding using a custom pattern from log4rs::encode::pattern
⮳ Assigns the configuration to log4rs::config::Config
⮳ and sets the default log::LevelFilter
⮳
use anyhow::Result; use log::LevelFilter; use log4rs::append::file::FileAppender; use log4rs::config::Appender; use log4rs::config::Config; use log4rs::config::Root; use log4rs::encode::pattern::PatternEncoder; fn main() -> Result<()> { let logfile = FileAppender::builder() .encoder(Box::new(PatternEncoder::new("{l} - {m}\n"))) .build("temp/log/output.log")?; let config = Config::builder() .appender(Appender::builder().build("logfile", Box::new(logfile))) .build(Root::builder().appender("logfile").build(LevelFilter::Info))?; log4rs::init_config(config)?; log::info!("Hello, world!"); Ok(()) }
Alternatives
Recipe | Crates | Categories |
---|---|---|
log | ||
slog | ||
log4rs | ||
env_logger | ||
OpenTelemetry | OpenTelemetry Rust documentation⮳ | |
OpenObserve | OpenObserve⮳ |
Use older alternatives to tracing
tracing
is now the "go-to" crate for logging, but log
, slog
and log4rs
are still in extensive use.
log
log
is an older and simpler crate, if your needs are simple and you are not using any async code.
slog
Structured, extensible, composable logging. slog
remains a stable, featureful and battle-tested library, used in many important projects.
fn main() { todo!(); }
log4rs
log4rs
is a highly configurable multi-output logging implementation for the log
facade.
use log::{error, info, warn}; use log4rs; fn main() { log4rs::init_file("config/log4rs.yaml", Default::default()).unwrap(); info!("booting up"); // ... }
env_logger
A logging implementation for log which is configured via an environment variable. env_logger
makes sense when used in executables (binary projects). Libraries should use the log crate instead.
use log::info; fn main() { env_logger::init(); info!("starting up"); // ... }
Other frameworks
OpenTelemetry
OpenTelemetry Rust documentation⮳
fn main() { todo!(); }
OpenObserve
OpenObserve⮳ (written in Rust) is a petabyte-scale Elasticsearch/Splunk/Datadog alternative for logs, metrics, traces, RUM, error tracking, and session replay.
fn main() { todo!(); }
Diagnostic functions
Recipe | Crates | Categories |
---|---|---|
Get the type name of the pointed-to value |
Get the type name of the pointed-to value
fn get_iter() -> impl Iterator<Item = i32> { [1, 2, 3].into_iter() } fn main() { let iter = get_iter(); // NEW in Rust 1.76 // Returns the type name of the pointed-to value as a string slice. let iter_name = std::any::type_name_of_val(&iter); let sum: i32 = iter.sum(); println!("The sum of the `{iter_name}` is {sum}."); // prints: The sum of the `core::array::iter::IntoIter<i32, 3>` is 6. }
- move to proper location
- cover
std::any::type_name
Encoding
Encoding and/or decoding data from one data format to another.
Character Sets
Recipe | Crates | Categories |
---|---|---|
Percent-encode a string | ||
Encode a string as application/x-www-form-urlencoded | ||
Encode and decode hexadecimal | ||
Encode and decode base64 |
CSV Processing
Structured Data
Recipe | Crates | Categories |
---|---|---|
Serialize and deserialize unstructured JSON | ||
Deserialize a TOML configuration file | ||
Read and write integers in little-endian byte order |
Serde
Recipe | Crates | Categories |
---|---|---|
Serialize JSON | ||
monostate | ||
serde-ignored |
Character Sets
Recipe | Crates | Categories |
---|---|---|
Percent-encode a string | ||
Encode a string as application/x-www-form-urlencoded | ||
Encode and decode hexadecimal | ||
Encode and decode base64 |
Percent-encode a string
Encode an input string with percent_encoding⮳ using the percent_encoding::utf8_percent_encode
⮳ function from the percent_encoding
crate. Then decode using the percent_encoding::percent_decode
⮳ function.
use std::str::Utf8Error; use percent_encoding::AsciiSet; use percent_encoding::CONTROLS; use percent_encoding::percent_decode; use percent_encoding::utf8_percent_encode; /// https://url.spec.whatwg.org/#fragment-percent-encode-set const FRAGMENT: &AsciiSet = &CONTROLS.add(b' ').add(b'"').add(b'<').add(b'>').add(b'`'); fn main() -> Result<(), Utf8Error> { let input = "confident, productive systems programming"; let iter = utf8_percent_encode(input, FRAGMENT); let encoded: String = iter.collect(); println!("{}", encoded); assert_eq!(encoded, "confident,%20productive%20systems%20programming"); let iter = percent_decode(encoded.as_bytes()); let decoded = iter.decode_utf8()?; println!("{}", decoded); assert_eq!(decoded, "confident, productive systems programming"); 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 std::string::String
⮳.
Encode a string as application/x-www-form-urlencoded
Encodes a string into application/x-www-form-urlencoded
syntax using the form_urlencoded::byte_serialize
⮳ and subsequently decodes it with form_urlencoded::parse
⮳. Both functions return iterators that collect into a std::string::String
⮳.
use url::form_urlencoded::byte_serialize; use url::form_urlencoded::parse; fn main() { let urlencoded: String = byte_serialize("What is ❤?".as_bytes()).collect(); assert_eq!(urlencoded, "What+is+%E2%9D%A4%3F"); println!("urlencoded:'{}'", urlencoded); let decoded: String = parse(urlencoded.as_bytes()) .map(|(key, val)| [key, val].concat()) .collect(); assert_eq!(decoded, "What is ❤?"); println!("decoded:'{}'", decoded); }
Encode and decode hexadecimal
The data_encoding
⮳ crate provides a HEXUPPER::encode
method which takes a &[u8]
and returns a std::string::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.
use data_encoding::DecodeError; use data_encoding::HEXUPPER; fn main() -> Result<(), DecodeError> { let original = b"The quick brown fox jumps over the lazy dog."; let expected = "54686520717569636B2062726F776E20666F78206A756D7073206F76\ 657220746865206C617A7920646F672E"; let encoded = HEXUPPER.encode(original); println!("{}", encoded); assert_eq!(encoded, expected); let decoded = HEXUPPER.decode(&encoded.into_bytes())?; println!("{:?}", decoded); assert_eq!(&decoded[..], &original[..]); Ok(()) }
Encode and decode base64
Encodes byte slice into base64
String using base64::encode
and decodes it with base64::decode
.
use std::str; use anyhow::Result; use base64::prelude::*; fn main() -> Result<()> { let hello = b"hello rustaceans"; let encoded: String = BASE64_STANDARD.encode(hello); let decoded: Vec<u8> = BASE64_STANDARD.decode(&encoded)?; println!("origin: {}", str::from_utf8(hello)?); println!("base64 encoded: {}", encoded); println!("back to origin: {}", str::from_utf8(&decoded)?); Ok(()) }
CSV processing
Read CSV records
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; fn main() -> Result<(), Error> { let csv = "year,make,model,description 1948,Porsche,356,Luxury sports car 1967,Ford,Mustang fastback 1967,American car"; let mut reader = csv::Reader::from_reader(csv.as_bytes()); for record in reader.records() { let record = record?; println!( "In {}, {} built the {} model. It is a {}.", &record[0], &record[1], &record[2], &record[3] ); } 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, } fn main() -> Result<(), csv::Error> { let csv = "year,make,model,description 1948,Porsche,356,Luxury sports car 1967,Ford,Mustang fastback 1967,American car"; let mut reader = csv::Reader::from_reader(csv.as_bytes()); for record in reader.deserialize() { let record: Record = record?; println!( "In {}, {} built the {} model. It is a {}.", record.year, record.make, record.model, record.description ); } Ok(()) }
Read CSV records with different delimiter
Reads CSV records with a tab csv::ReaderBuilder::delimiter
⮳.
use csv::Error; use csv::ReaderBuilder; use serde::Deserialize; #[derive(Debug, Deserialize)] struct Record { name: String, place: String, #[serde(deserialize_with = "csv::invalid_option")] id: Option<u64>, } fn main() -> Result<(), Error> { let data = "name\tplace\tid Mark\tMelbourne\t46 Ashley\tZurich\t92"; let mut reader = ReaderBuilder::new() .delimiter(b'\t') .from_reader(data.as_bytes()); for result in reader.deserialize::<Record>() { println!("{:?}", result?); } Ok(()) }
Filter CSV records matching a predicate
Returns only the rows from data
with a field that matches query
.
use std::io; use anyhow::Result; fn main() -> Result<()> { let query = "CA"; let data = "\ City,State,Population,Latitude,Longitude Kenai,AK,7610,60.5544444,-151.2583333 Oakman,AL,,33.7133333,-87.3886111 Sandfort,AL,,32.3380556,-85.2233333 West Hollywood,CA,37031,34.0900000,-118.3608333"; let mut rdr = csv::ReaderBuilder::new().from_reader(data.as_bytes()); let mut wtr = csv::Writer::from_writer(io::stdout()); wtr.write_record(rdr.headers()?)?; for result in rdr.records() { let record = result?; if record.iter().any(|field| field == query) { wtr.write_record(&record)?; } } wtr.flush()?; Ok(()) }
This example has been adapted from the csv crate tutorial⮳
Handle invalid CSV data with serde
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 std::option::Option::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>, } fn main() -> Result<(), Error> { let data = "name,place,id mark,sydney,46.5 ashley,zurich,92 akshat,delhi,37 alisha,colombo,xyz"; let mut rdr = csv::Reader::from_reader(data.as_bytes()); for result in rdr.deserialize() { let record: Record = result?; println!("{:?}", record); } Ok(()) }
Serialize records to CSV
This example shows how to serialize a Rust tuple. csv::writer
⮳ supports automatic serialization from Rust types into CSV records. csv::Writer::write_record
⮳ writes a simple record containing string data only. Data with more complex values such as numbers, floats, and options use csv::Writer::serialize
⮳. Since CSV writer uses an internal buffer, always explicitly csv::Writer::flush
⮳ when done.
use std::io; use anyhow::Result; fn main() -> Result<()> { let mut wtr = csv::Writer::from_writer(io::stdout()); wtr.write_record(["Name", "Place", "ID"])?; wtr.serialize(("Mark", "Sydney", 87))?; wtr.serialize(("Ashley", "Dublin", 32))?; wtr.serialize(("Akshat", "Delhi", 11))?; wtr.flush()?; Ok(()) }
Serialize records to CSV using serde
The following example shows how to serialize custom structs as CSV records using the serde
⮳ crate.
use std::io; use anyhow::Result; use serde::Serialize; #[derive(Serialize)] struct Record<'a> { name: &'a str, place: &'a str, id: u64, } fn main() -> Result<()> { let mut wtr = csv::Writer::from_writer(io::stdout()); let rec1 = Record { name: "Mark", place: "Melbourne", id: 56, }; let rec2 = Record { name: "Ashley", place: "Sydney", id: 64, }; let rec3 = Record { name: "Akshat", place: "Delhi", id: 98, }; wtr.serialize(rec1)?; wtr.serialize(rec2)?; wtr.serialize(rec3)?; wtr.flush()?; Ok(()) }
Transform a CSV column
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.
See csv::Reader::deserialize
⮳, serde::Deserialize
⮳ and std::str::FromStr
⮳.
use std::str::FromStr; use anyhow::Result; use anyhow::anyhow; use serde::Deserialize; use serde::Deserializer; use serde::de; #[derive(Debug, Deserialize)] struct Row { color_name: String, color: HexColor, } #[derive(Debug)] struct HexColor { red: u8, green: u8, blue: u8, } impl FromStr for HexColor { type Err = anyhow::Error; fn from_str(hex_color: &str) -> std::result::Result<Self, Self::Err> { let trimmed = hex_color.trim_matches('#'); if trimmed.len() != 6 { Err(anyhow!("Invalid length of hex string")) } else { Ok(HexColor { red: u8::from_str_radix(&trimmed[..2], 16)?, green: u8::from_str_radix(&trimmed[2..4], 16)?, blue: u8::from_str_radix(&trimmed[4..6], 16)?, }) } } } impl<'de> Deserialize<'de> for HexColor { fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error> where D: Deserializer<'de>, { let s = String::deserialize(deserializer)?; FromStr::from_str(&s).map_err(de::Error::custom) } } fn main() -> Result<()> { let data = "color_name,color red,#ff0000 green,#00ff00 blue,#0000FF periwinkle,#ccccff magenta,#ff00ff" .to_owned(); let mut out = csv::Writer::from_writer(vec![]); let mut reader = csv::Reader::from_reader(data.as_bytes()); // Deserialize as Row, using the implementation above for result in reader.deserialize() { // We need to provide a type hint for automatic deserialization. let res: Row = result?; // Serialize the tuple as CSV into Vec<u8> out.serialize(( res.color_name, res.color.red, res.color.green, res.color.blue, ))?; } let written = String::from_utf8(out.into_inner()?)?; assert_eq!(Some("magenta,255,0,255"), written.lines().last()); println!("{}", written); Ok(()) }
Structured Data
Recipe | Crates | Categories |
---|---|---|
Serialize and deserialize unstructured JSON | ||
Deserialize a TOML configuration file | ||
Read and write integers in little-endian byte order |
Serialize and deserialize unstructured JSON
The serde_json
⮳ crate provides a serde_json::from_str
⮳ function to parse a &str
of JSON.
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 serde_json::json
⮳ macro.
use serde_json::Error; use serde_json::Value; use serde_json::json; fn main() -> Result<(), Error> { let j: &str = r#"{ "userid": 103609, "verified": true, "access_privileges": [ "user", "admin" ] }"#; let parsed: Value = serde_json::from_str(j)?; let expected: Value = json!({ "userid": 103609, "verified": true, "access_privileges": [ "user", "admin" ] }); println!("{}", expected); assert_eq!(parsed, expected); Ok(()) }
Deserialize a TOML configuration file
TOML is a simple, ergonomic, and readable configuration format that is often used by Rust's tooling - for example cargo
.
The following parses some TOML into a universal toml::Value
that is able to represent any valid TOML data.
use toml::Value; use toml::de::Error; fn main() -> Result<(), Error> { // Note the use of a raw string, // so that there is no need to escape the inner double quotes let toml_content = r#" [package] name = "your_package" version = "0.1.0" authors = ["You! <you@example.org>"] [dependencies] serde = "1.0" "#; let package_info: Value = toml::from_str(toml_content)?; assert_eq!(package_info["dependencies"]["serde"].as_str(), Some("1.0")); println!( "Package name: {}", package_info["package"]["name"].as_str().unwrap() ); Ok(()) }
Parse TOML into your own structs using serde
⮳.
use std::collections::HashMap; use serde::Deserialize; use toml::de::Error; #[derive(Deserialize, Debug)] struct Config { package: Package, dependencies: HashMap<String, String>, } #[derive(Deserialize, Debug)] struct Package { name: String, version: String, authors: Vec<String>, } fn main() -> Result<(), Error> { let toml_content = r#" [package] name = "your_package" version = "0.1.0" authors = ["You! <you@example.org>"] [dependencies] serde = "1.0" "#; let package_info: Config = toml::from_str(toml_content)?; println!("{:?}", package_info); 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(()) }
Read and write integers in little-endian byte order
byteorder
⮳ is a library for reading/writing numbers in big-endian and little-endian. It can reverse the significant bytes of structured data. This may be necessary when receiving information over the network, when bytes received are from another system.
use std::io::Error; use byteorder::LittleEndian; use byteorder::ReadBytesExt; use byteorder::WriteBytesExt; #[derive(Default, PartialEq, Debug)] struct Payload { kind: u8, value: u16, } fn encode(payload: &Payload) -> Result<Vec<u8>, Error> { let mut bytes = vec![]; bytes.write_u8(payload.kind)?; bytes.write_u16::<LittleEndian>(payload.value)?; Ok(bytes) } fn decode(mut bytes: &[u8]) -> Result<Payload, Error> { let payload = Payload { kind: bytes.read_u8()?, value: bytes.read_u16::<LittleEndian>()?, }; Ok(payload) } fn main() -> Result<(), Error> { let original_payload = Payload::default(); let encoded_bytes = encode(&original_payload)?; println!("{:?}", encoded_bytes); let decoded_payload = decode(&encoded_bytes)?; assert_eq!(original_payload, decoded_payload); println!("{:?}", decoded_payload); Ok(()) }
Serialization
Recipe | Crates | Categories |
---|---|---|
Serialize JSON | ||
monostate | ||
serde-ignored |
De facto standard serialization library. Use in conjunction with sub-crates like serde_json for the specific format that you are using.
fn main() { todo!(); }
Serialize JSON
fn main() { todo!(); }
See also
monostate
This library implements a type macro for a zero-sized type that is Serde deserializable only from one specific value.
fn main() { todo!(); }
serde-ignored
fn main() { todo!(); }
dedupe JSON with complex.md Supported formats
Typecasts
bytemuck
fn main() { todo!(); }
zerocopy
fn main() { todo!(); }
Binary encoders
Recipe | Crates | Categories |
---|---|---|
bincode | ||
CBOR with ciborium | ||
prost | ||
protobuf | ||
MessagePack with rmp-serde |
bincode
A binary serialization / deserialization strategy for transforming structs into bytes and vice versa.
ProtoBuf
prost
prost
is a Protocol Buffers implementation for the Rust Language.
protobuf
protobuf
is a Rust implementation of Google protocol buffers.
MessagePack with rmp-serde
This crate connects Rust MessagePack library with serde providing an ability to easily serialize and deserialize both Rust built-in types, the standard library and custom data structures.
CBOR with ciborium
Concise Binary Object Representation is a binary data serialization format loosely based on JSON. ciborium
is a serde
implementation of CBOR using ciborium-basic.
Error Handling
Trigger and handle irrecoverable panics
The panic!(...)
macro allows a program to terminate immediately and provide feedback to the caller of the program.
fn main() { panic!("Crash and burn"); }
panic!
is closely tied with the unwrap
method of both Option
and Result
enums. Both implementations call panic!
when they are set to None
or Err
variants.
// use std::str::FromStr; fn main() { let number_str = "42"; // `parse()` attempts to convert the string into a number. // This operation can fail if the string isn't a valid number, so it returns // a `Result<u32, ParseIntError>`. let number: u32 = number_str.parse().unwrap(); // `unwrap()` is called on the `Result` to extract the `u32` value. // - If the parsing is successful, the value is assigned to `number`. // - If the parsing fails, the program panics with an error message. println!("The number is: {}", number); }
Provide a fallback value with unwrap_or_else
use std::fs; use std::fs::File; use std::io::ErrorKind; fn main() { if !fs::exists("temp").unwrap() { fs::create_dir("temp").unwrap(); } let _greeting_file = File::open("temp/hello.txt").unwrap_or_else(|error| { if error.kind() == ErrorKind::NotFound { File::create("temp/hello.txt").unwrap_or_else(|error| { panic!("Problem creating the file: {:?}", error); }) } else { panic!("Problem opening the file: {:?}", error); } }); }
Return recoverable errors with Result
use std::io; use std::io::BufRead; fn main() { let mut cursor = io::Cursor::new(b"foo\nbar"); let mut buf = String::new(); cursor // `read_line` puts whatever the user enters into the string we pass to it, // but it also returns a `Result` value. .read_line(&mut buf) // If this instance of `Result` is an `Err` value, expect will cause the program to crash // and display the message that you passed as an argument to expect. .expect("Failed to read line"); // Alternative: `unwrap` panics if there is an error // let _greeting_file = std::fs::File::open("temp/hello.txt").unwrap(); }
Propagate errors with the ?
operator
use std::fs::File; use std::io; use std::io::Read; fn read_username_from_file() -> Result<String, io::Error> { let mut username_file = File::open("temp/hello.txt")?; let mut username = String::new(); username_file.read_to_string(&mut username)?; Ok(username) } fn main() { if !std::fs::exists("temp").unwrap() { std::fs::create_dir("temp").unwrap(); } match read_username_from_file() { Ok(name) => println!("User name: {}", name), Err(err) => println!("Error: {}", err), } }
If the value of the Result is an Ok
, the value inside the Ok
will get returned from this expression, and the program will continue. If the value is an Err
, the Err
will be returned from the whole function, as if we had used the return
keyword, so the error value gets propagated to the calling code.
This error points out that we’re only allowed to use the ?
operator in a function that returns Result
, Option
, or another type that implements std::ops::FromResidual
⮳.
Another example:
use std::error::Error; fn parse_port(s: &str) -> Result<u16, Box<dyn Error>> { // We need to use `Box<dyn Error>`, because the returned error type // cannot be determined during compile time: It will either // contain an instance of `std::num::ParseIntError` (from the parse // method, when parsing fails), or a string (when the port is // zero). Alternatively, you may use `anyhow::Result`. let port: u16 = s.parse()?; if port == 0 { Err(Box::from(format!("Invalid port: {}", port))) } else { Ok(port) } } fn main() { match parse_port("123") { Ok(port) => println!("Port: {}", port), Err(err) => panic!("{}", err), } }
std::io
defines the type alias type Result<T> = std::result::Result<T, std::io::Error>;
Handle errors correctly in main
std::io::Error
⮳ defined type implementing the std::error::Error
⮳ trait.
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. It returns the uptime, unless there is an error.
use std::fs::File; use std::io::Read; use anyhow::Result; use anyhow::anyhow; fn read_uptime() -> Result<u64> { let mut uptime = String::new(); File::open("/proc/uptime")?.read_to_string(&mut uptime)?; Ok(uptime .split('.') .next() .ok_or(anyhow!("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
Uses reqwest
⮳::blocking⮳ to query a random integer generator web service. Converts the string response into an integer.
fn parse_response( response: reqwest::blocking::Response, ) -> anyhow::Result<u32> { let body = response.text()?; let body = body.trim(); // println!("Body: {body}"); let b = body.parse::<u32>()?; Ok(b) } fn main() -> anyhow::Result<()> { let url = "https://www.random.org/integers/?num=1&min=0&max=10&col=1&base=10&format=plain".to_string(); let response = reqwest::blocking::get(url)?; let random_value: u32 = parse_response(response)?; println!("A random number between 0 and 10: {}", random_value); Ok(()) }
Obtain the backtrace in complex error scenarios
This recipe shows how to handle a complex error scenario and then print a backtrace. It relies on to extend errors by appending new errors.
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 std::fmt; // use anyhow::anyhow; // use anyhow::Context; use anyhow::Result; // use serde::Deserialize; // #[derive(Debug, Deserialize)] // struct Rgb { // red: u8, // blue: u8, // green: u8, // } // impl Rgb { // fn from_reader(csv_data: &[u8]) -> Result<Rgb> { // let c = csv::Reader::from_reader(csv_data) // .deserialize() // .nth(0) // .ok_or(anyhow!(""))?; // let color = c.context("")?; // Ok(color) // } // } // impl fmt::UpperHex for Rgb { // fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { // let hexa = u32::from(self.red) << 16 // | u32::from(self.blue) << 8 // | u32::from(self.green); // write!(f, "{:X}", hexa) // } // } // fn main() -> Result<()> { // let csv = "red,blue,green // 102,256,204"; // let rgb = Rgb::from_reader(csv.as_bytes())?; // println!("{:?} to hexadecimal #{:X}", rgb, rgb); // Ok(()) // } fn main() -> Result<()> { Ok(()) }
Backtrace error rendered:
Error level - description
└> 0 - Cannot read CSV data
└> 1 - Cannot deserialize RGB color
└> 2 - CSV deserialize error: record 1 (line: 2, byte: 15): field 1: number too large to fit in target type
└> 3 - field 1: number too large to fit in target type
Run the recipe with RUST_BACKTRACE=1
to display a detailed backtrace associated with this error.
error_handling: need examples for (P1)
- unwrap
Custom Errors
Recipe | Crates | Categories |
---|---|---|
anyhow | ||
thisError | ||
miette | ||
color-eyre |
Use anyhow
⮳ if you don't care what error type your functions return, you just want it to be easy. This is common in application code. Use thiserror
⮳ if you are a library that wants to design your own dedicated error type(s) so that on failures the caller gets exactly the information that you choose.
anyhow
anyhow
provides a flexible concrete Error type built on std::error::Error
.
Use Result<T, anyhow::Error>
or equivalently anyhow::Result<a name="a008"></a><T>
⮳ as the return type of any fallible function.
use anyhow::Context; use anyhow::Result; fn do_something() -> Result<()> { Err(anyhow::Error::msg("Some Error")) } fn main() -> anyhow::Result<()> { // ... do_something().context("Failed to do the important thing")?; // Provide context to the error let _content = std::fs::read("/notafile.txt") .with_context(|| "Failed to read instrs from file".to_string())?; Ok(()) }
Anyhow works with any error type that has an impl of std::error::Error
, including ones defined in your crate e.g. using thiserror
⮳.
thisError
thiserror
⮳ provides a convenient derive
⮳ macro for the standard library’s std::error::Error
trait.
use thiserror::Error; #[derive(Error, Debug)] pub enum DataStoreError { // A Display impl is generated for your error if you provide // #[error("...")] messages on the struct or each variant of your enum #[error("data store disconnected")] Disconnect(#[from] std::io::Error), /* A From impl is generated for * each variant containing * a #[from] attribute. */ #[error("the data for key `{0}` is not available")] Redaction(String), #[error("invalid header (expected {expected:?}, found {found:?})")] InvalidHeader { expected: String, found: String }, #[error("unknown data store error")] Unknown, #[error(transparent)] // Forward the source and Display methods straight through to an // underlying error without adding an additional message. Other(#[from] anyhow::Error), } fn main() -> Result<(), Box<dyn std::error::Error>> { // Return an error: Err(DataStoreError::Unknown)?; Ok(()) }
The #[error(...)]
messages support a shorthand for interpolating fields from the error.
#[error("{var}")] //⟶ write!("{}", self.var)
#[error("{0}")] //⟶ write!("{}", self.0)
#[error("{var:?}")] //⟶ write!("{:?}", self.var)
#[error("{0:?}")] //⟶ write!("{:?}", self.0)
use thiserror::Error; #[derive(Error, Debug)] pub struct MyError { msg: String, // The Error trait’s source() method is implemented to return whichever // field has a #[source] attribute or is named source, if any. This is // for identifying the underlying lower level error that caused your // error. #[from] implies #[source]. Any error type that implements // `std::error::Error` or dereferences to `dyn std::error::Error` will work // as a source. #[source] source: std::io::Error, // Automatically detected to implement provide() // backtrace: std::backtrace::Backtrace, } impl std::fmt::Display for MyError { fn fmt( &self, f: &mut std::fmt::Formatter<'_>, ) -> Result<(), std::fmt::Error> { write!(f, "{}", self.msg) } } fn example() -> Result<(), Box<dyn std::error::Error>> { let io_error = std::io::Error::new(std::io::ErrorKind::Other, "oh no!"); Err(Box::new(MyError { msg: "Error message".to_string(), source: io_error, })) } fn main() { match example() { Ok(_) => { println!("Got OK"); } Err(err) => { println!("Got {}", err); } } }
miette
miette
is a fancy diagnostic reporting library and protocol.
mod mylib { //! library code: define unique error types and error wrappers use miette::Diagnostic; use miette::NamedSource; use miette::Result; use miette::SourceSpan; // You can derive a `Diagnostic` from any `std::error::Error` type. // `thiserror` plays nicely with `miette` use thiserror::Error; #[derive(Error, Diagnostic, Debug)] pub enum MyLibError { #[error("A bad thing happened!")] // provided by `thisError` #[diagnostic( // Use `#[diagnostic(code(...))]` to set the unique code for this error. code(my_lib::bad_thing), // Set the URL that will be displayed as an actual link in supported terminals. // `url(docsrs)` automatically create a link to this diagnostic on docs.rs // or use a custom URL like `url("https://my_website.com/error_codes#{}", self.code)` url(docsrs), // Supply help text help("try doing it better next time?"))] BadThingHappened, #[error("Something went wrong!")] SomethingWentWrong { // The Source that we're gonna be printing snippets out of. // This can be a String if you don't have or care about file names. #[source_code] src: NamedSource<String>, // Snippets and highlights can be included in the diagnostic! // You may also use `(usize, usize)`, the byte-offset and length // into an associated SourceCode or // `Option<SourceSpan>` #[label("This bit highlighted here is the problem")] bad_bit: SourceSpan, // Programmatically supply the help text #[help] advice: Option<String>, // Can also be `String` // Related errors #[related] others: Vec<MyLibError>, }, // Wrap an Error #[error(transparent)] // Forward the source and Display methods // straight through to the underlying error. #[diagnostic(code(my_lib::io_error))] IoError(#[from] std::io::Error), // Wrap another Diagnostic // Use `#[diagnostic(transparent)]` to wrap another `[Diagnostic]` // You won't see labels otherwise #[error(transparent)] #[diagnostic(transparent)] AnotherError(#[from] AnotherError), } #[derive(Error, Diagnostic, Debug)] #[error("another error")] pub struct AnotherError { #[label("here")] pub at: SourceSpan, } pub fn this_fails() -> Result<()> { // You can use plain strings as a `Source`, // or anything that implements the one-method `Source` trait. let src = "source\n text\n here".to_string(); // You may also use `map_err(|error| { // error.with_source_code(String::from("source code")) })` later. Err(MyLibError::SomethingWentWrong { src: NamedSource::new("bad_file.rs", src), bad_bit: (9, 4).into(), advice: Some("Some help text".to_string()), others: vec![MyLibError::BadThingHappened], })?; Ok(()) } } use miette::Result; /// To get errors printed nicely in application code, just return a /// `Result<()>` /// /// Note: You can swap out the default reporter for a /// custom one using `miette::set_hook()` fn main() -> Result<()> { mylib::this_fails()?; Ok(()) }
color-eyre
color-eyre
is an error report handler for panics and eyre::Reports
for colorful, consistent, and well formatted error reports for all kinds of errors.
It is a fork of anyhow
that gives you more control over the format of the generated error messages. It is recommended if you intend to present error messages to end users. Otherwise anyhow
is simpler.
fn main() { todo!(); }
See also
Do not use Error Chain⮳, which is deprecated.
File System
Dealing with files and file systems.
File Reading & Writing
Recipe | Crates | Categories |
---|---|---|
Read lines of strings from a file | ||
Avoid writing and reading from the same file | ||
Access a file randomly using a memory map |
Current Working Directory
Recipe | Crates | Categories |
---|---|---|
Get the current working directory |
User Directories
Recipe | Crates | Categories |
---|---|---|
dirs | ||
directories |
Temporary Files and Directories
Recipe | Crates | Categories |
---|---|---|
Create temporary files or temporary directories |
Directory Traversal
| globset
| | |
Walk the Filesystem while Respecting Ignore Files
Recipe | Crates | Categories |
---|---|---|
Walk the filesystem while respecting ignore files |
File Watching
Recipe | Crates | Categories |
---|---|---|
Watch files or directories and execute a function when they change |
Read & Write
Recipe | Crates | Categories |
---|---|---|
Read lines of strings from a file | ||
Avoid writing and reading from the same file | ||
Access a file randomly using a memory map |
Read lines of strings from a file
Writes a three-line message to a file, then reads it back a line at a time with the std::io::Lines
⮳ iterator created by
std::io::BufRead::lines
⮳ std::fs::File
⮳ implements std::io::Read
⮳ which provides std::io::BufReader
⮳ trait. std::fs::File::create
⮳ opens a std::fs::File
⮳ for writing, std::fs::File::open
⮳ for reading.
use std::fs; use std::fs::File; use std::io::BufRead; use std::io::BufReader; use std::io::Error; use std::io::Write; fn main() -> Result<(), Error> { if !fs::exists("temp")? { fs::create_dir("temp")?; } let path = "temp/lines.txt"; let mut output = File::create(path)?; write!(output, "Rust\n💖\nFun")?; let input = File::open(path)?; let buffered = BufReader::new(input); for line in buffered.lines() { println!("{}", line?); } Ok(()) }
Avoid writing and reading from the same file
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 std::fs; use std::fs::File; use std::io::BufRead; use std::io::BufReader; use std::io::Error; use std::io::ErrorKind; use std::path::Path; use same_file::Handle; fn main() -> Result<(), Error> { if !fs::exists("temp")? { fs::create_dir("temp")?; } std::fs::write("temp/new.txt", b"Lorem ipsum")?; let path_to_read = Path::new("temp/new.txt"); let stdout_handle = Handle::stdout()?; let handle = Handle::from_path(path_to_read)?; 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
displays the contents of the file new.txt
.
cargo run >> new.txt
errors because the two files are same.
Access a file randomly using a memory map
Creates a memory map of a file using memmap2
⮳ and simulates some non-sequential reads from the file. Using a memory map means you just index into a slice rather than dealing with std::fs::File::seek
⮳ to navigate a std::fs::File
⮳.
The memmap2::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 std::fs; use std::fs::File; use std::io::Error; use std::io::Write; use memmap2::Mmap; fn main() -> Result<(), Error> { if !fs::exists("temp")? { fs::create_dir("temp")?; } write!( File::create("temp/content.txt")?, "My hovercraft is full of eels!" )?; let file = File::open("temp/content.txt")?; let map = unsafe { Mmap::map(&file)? }; println!("{:?}", map); let random_indexes = [0, 1, 2, 19, 22, 10, 11, 29]; assert_eq!(&map[3..13], b"hovercraft"); let random_bytes: Vec<u8> = random_indexes.iter().map(|&idx| map[idx]).collect(); assert_eq!(&random_bytes[..], b"My loaf!"); Ok(()) }
Current working directory
Recipe | Crates | Categories |
---|---|---|
Get the current working directory |
Get the current working directory
use std::env; use anyhow::Result; fn main() -> Result<()> { let cwd = env::current_dir()?; println!("The current directory is {}", cwd.display()); Ok(()) }
Directory Traversal
| globset
| | |
Find files that have been modified in the last 24 hours
Gets the current working directory by calling std::env::current_dir
⮳ then for each entries in std::fs::read_dir
⮳ extracts the
std::fs::DirEntry::path
⮳ and gets the metadata via std::fs::Metadata
⮳. The
std::fs::Metadata::modified
⮳ returns the std::time::SystemTime::elapsed
⮳ time since last modification. std::time::Duration::as_secs
⮳ converts the time to seconds and compared with 24 hours (24 60 60 seconds). std::fs::Metadata::is_file
⮳ filters out directories.
use std::env; use std::fs; use anyhow::Result; use anyhow::anyhow; fn main() -> Result<()> { let current_dir = env::current_dir()?; println!( "Entries modified in the last 24 hours in {:?}:", current_dir ); for entry in fs::read_dir(current_dir)? { let entry = entry?; let path = entry.path(); let metadata = fs::metadata(&path)?; if let Ok(time) = metadata.modified() { // Note: SystemTime.elapsed can be flaky. if let Ok(duration) = time.elapsed() { let last_modified = duration.as_secs(); if (last_modified < 24 * 3600) && metadata.is_file() { println!( "Last modified: {:?} seconds, is read only: {:?}, size: {:?} bytes, filename: {:?}", last_modified, metadata.permissions().readonly(), metadata.len(), path.file_name().ok_or(anyhow!("No filename"))? ); } } } else { println!("Last modification time not supported on this platform"); } } Ok(()) }
Find loops for a given path
same-file
is a simple crate for determining whether two file paths point to the same file.
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
The following would assert that a loop exists.
#![cfg(target_os = "linux")] use std::io; use std::path::Path; use std::path::PathBuf; use same_file::is_same_file; // Returns the two paths that form a loop, if found // Returns None otherwise // P: AsRef<Path> accepts PathBuf, Path... fn contains_loop<P: AsRef<Path>>( path: P, ) -> io::Result<Option<(PathBuf, PathBuf)>> { let path: &Path = path.as_ref(); // Copy into a mutable PathBuf let mut path_buf: PathBuf = path.to_path_buf(); // Truncate path_buf in succession: /stuff/much -> /stuff -> / while path_buf.pop() { if is_same_file(&path_buf, path)? { return Ok(Some((path_buf, path.to_path_buf()))); // Investigate the parent path against its own parents as well } else if let Some((looped_path1, looped_path2)) = contains_loop(&path_buf)? { return Ok(Some((looped_path1, looped_path2))); } } Ok(None) } fn main() { // `is_same_file` returns true if the two file paths may correspond to the // same file. assert!(is_same_file("/tmp/foo", "/tmp/./foo").unwrap_or(false)); assert_eq!( contains_loop("/tmp/foo/bar/baz/qux/bar/baz").unwrap(), Some(( PathBuf::from("/tmp/foo"), PathBuf::from("/tmp/foo/bar/baz/qux") )) ); println!("Loop found."); }
Recursively find duplicate file names
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(); for entry in WalkDir::new(".") .into_iter() .filter_map(Result::ok) .filter(|e| !e.file_type().is_dir()) { let f_name = String::from(entry.file_name().to_string_lossy()); let counter = filenames.entry(f_name.clone()).or_insert(0); *counter += 1; if *counter == 2 { println!("{}", f_name); } } }
Recursively find all files with a given predicate
Find files modified within the last day in the current directory. Using walkdir::WalkDir::follow_links
⮳ ensures symbolic links are followed like they were normal directories and files.
use anyhow::Result; use walkdir::WalkDir; fn main() -> Result<()> { for entry in WalkDir::new(".") .follow_links(true) .into_iter() .filter_map(|e| e.ok()) { let f_name = entry.file_name().to_string_lossy(); // `metadata()` can return errors for path values that the program // does not have permissions to access or if the path no longer exists. if let Ok(metadata) = entry.metadata() { let sec = metadata.modified()?; if let Ok(elapsed) = sec.elapsed() { if elapsed.as_secs() < 86400 { println!("{}", f_name); } } } // You may also check for specific extensions: // && f_name.ends_with(".json") } Ok(()) }
Traverse directories while skipping dotfiles
Uses walkdir::IntoIter::filter_entry
⮳ to descend recursively into entries passing the is_not_hidden
predicate thus skipping hidden files and directories. std::iter::Iterator::filter
⮳ applies to each walkdir::IntoIter::filter_entry
⮳ even if the parent is a hidden directory.
Root dir "."
yields through walkdir::WalkDir::depth
usage in is_not_hidden
predicate.
use walkdir::DirEntry; use walkdir::WalkDir; fn is_not_hidden(entry: &DirEntry) -> bool { entry .file_name() .to_str() .map(|s| entry.depth() == 0 || !s.starts_with('.')) .unwrap_or(false) } fn main() { let w = WalkDir::new("."); w.into_iter() .filter_entry(is_not_hidden) .filter_map(|v| v.ok()) .for_each(|x| println!("{}", x.path().display())); }
Recursively calculate file sizes at a given depth
Recursion depth can be flexibly set by walkdir::Walkdir::min_depth
⮳ & walkdir::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()); println!("Total size: {} bytes.", total_size); }
Find all files with a given extension recursively
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 anyhow::Result; use glob::glob; fn main() -> Result<()> { for entry in glob("**/*.png")? { println!("{}", entry?.display()); } Ok(()) }
Find all files with given pattern, ignoring filename case
Find all image files in the /media/
directory matching the img_[0-9][0-9]*.png
pattern.
A custom glob::MatchOptions
⮳ struct is passed to the glob::glob_with
⮳ function making the glob pattern case insensitive while keeping the other options std::default::Default
⮳.
use anyhow::Result; use glob::MatchOptions; use glob::glob_with; fn main() -> Result<()> { let options = MatchOptions { case_sensitive: false, ..Default::default() }; for entry in glob_with("/media/img_[0-9]*.png", options)? { println!("{}", entry?.display()); } Ok(()) }
globset
globset
allows multiple globs to be evaluated at once. Glob set matching is the process of matching one or more glob patterns against a single candidate path simultaneously, and returning all of the globs that matched.
Walk the filesystem
Recipe | Crates | Categories |
---|---|---|
Walk the filesystem while respecting ignore files |
Walk the filesystem while respecting ignore files
ignore
is a library for efficiently matching ignore files such as .gitignore
against file paths.
Recursive filesystem walking that respects ignore files (like .gitignore)
fn main() { todo!(); }
File watching
Recipe | Crates | Categories |
---|---|---|
Watch files or directories and execute a function when they change |
Watch files or directories and execute a function when they change
notify
is a cross-platform filesystem notification library.
use std::path::Path; use notify::EventHandler; use notify::RecursiveMode; use notify::Result; use notify::Watcher; use notify::event::Event; /// Prints received events struct EventPrinter; impl EventHandler for EventPrinter { fn handle_event(&mut self, res_event: Result<Event>) { match res_event { Ok(event) => println!("event: {:?}", event), Err(e) => println!("watch error: {:?}", e), } } } fn main() -> Result<()> { // Automatically select the best implementation for your platform. let mut watcher = notify::recommended_watcher(EventPrinter)?; // Add a path to be watched. All files and directories at that path // and below will be monitored for changes. watcher.watch(Path::new("."), RecursiveMode::Recursive)?; Ok(()) }
Temporary files and directories
Recipe | Crates | Categories |
---|---|---|
Create temporary files or temporary directories |
Create temporary files or temporary directories
tempfile
supports both temporary files and temporary directories.
fn main() { todo!(); }
- tempdir
User directories
Recipe | Crates | Categories |
---|---|---|
dirs | ||
directories |
Get platform-specific locations for configuration, cache, and other data
dirs
dirs
is a low-level library that provides platform-specific standard locations of directories for config, cache and other data on Linux, Windows, macOS and Redox by leveraging the mechanisms defined by the XDG base/user directory specifications on Linux, the Known Folder API on Windows, and the Standard Directory guidelines on macOS.
fn main() { todo!(); }
directories
directories
is a mid-level library that provides platform-specific standard locations of directories for config, cache and other data on Linux, Windows and macOS by leveraging the mechanisms defined by the XDG base/user directory specifications on Linux, the Known Folder API on Windows, and the Standard Directory guidelines on macOS.
directories
is a higher-level library than dirs
and can also compute paths for applications.
fn main() { todo!(); }
Hardware Support
Interface with specific CPU or other hardware features.
Processor
Recipe | Crates | Categories |
---|---|---|
Check the number of logical cpu cores |
Processor
Recipe | Crates | Categories |
---|---|---|
Check the number of logical cpu cores |
Check the number of logical cpu cores
Shows the number of logical CPU cores in the current machine using num_cpus::get
⮳.
fn main() { println!("Number of logical cores is {}", num_cpus::get()); }
Mathematics
Crates with a mathematical aspect.
Linear algebra
Recipe | Crates | Categories |
---|---|---|
Calculate vector norms | ||
Add matrices | ||
Multiply matrices | ||
Multiply a scalar with a vector and a matrix | ||
Invert a matrix | ||
Compare vectors | ||
(De)serialize a matrix |
Trigonometry
Recipe | Crates | Categories |
---|---|---|
Calculate the side length of a triangle | ||
Verify that tan is equal to sin divided by cos | ||
Calculate the distance between two points on Earth |
Complex numbers
Recipe | Crates | Categories |
---|---|---|
Create complex numbers | ||
Add complex numbers | ||
Use mathematical functions on complex numbers |
Statistics
Recipe | Crates | Categories |
---|---|---|
Calculate measures of central tendency | ||
Compute the standard deviation |
Additional numeric types
Recipe | Crates | Categories |
---|---|---|
Abstract over different number types | ||
Use big integers | ||
Use big decimals | ||
Sort floats |
| num-bigint
| | |
| num
| | |
| rug
| | |
Linear Algebra
Recipe | Crates | Categories |
---|---|---|
Calculate vector norms | ||
Add matrices | ||
Multiply matrices | ||
Multiply a scalar with a vector and a matrix | ||
Invert a matrix | ||
Compare vectors | ||
(De)serialize a matrix |
Add matrices
Creates two 2-D matrices with ndarray::arr2
⮳ and sums them element-wise.
Note that 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]]); let b = arr2(&[[6, 5, 4], [3, 2, 1]]); let sum = &a + &b; println!("{}", a); println!("+"); println!("{}", b); println!("="); println!("{}", sum); }
Multiply matrices
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]]); let b = arr2(&[[6, 3], [5, 2], [4, 1]]); println!("{}", a.dot(&b)); }
Multiply a scalar with a vector and a matrix
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 ndarray::Array2::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 ndarray::Array2::dot
⮳ handles it as a column vector.
use ndarray::Array1; use ndarray::arr1; use ndarray::arr2; fn main() { let scalar = 4; let vector = arr1(&[1, 2, 3]); let matrix = arr2(&[[4, 5, 6], [7, 8, 9]]); let new_vector: Array1<_> = scalar * vector; println!("{}", new_vector); let new_matrix = matrix.dot(&new_vector); println!("{}", new_matrix); }
Compare vectors
The ndarray
⮳ crate supports a number of ways to create arrays -- this recipe creates
ndarray::Array
⮳ from std::Vec
using std::convert::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 approx::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; assert_abs_diff_eq!(z, Array::from(vec![6., 6., 6., 6., 6.])); println!("c = {}", c); c[0] = 10.; d[1] = 10.; assert_abs_diff_eq!(w, Array::from(vec![6., 6., 6., 6., 6.])); }
Calculate vector norms
This recipe demonstrates use of the ndarray::Array1
⮳ type, ndarray::Array1
⮳ type,
ndarray::ArrayBase::fold
method, and ndarray::ArrayBase::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. + Thel1_norm
⮳ function is computed by andarray::ArrayBase::fold
⮳ operation that sums the absolute values of the elements. (This could also be performed withx.mapv(f64::abs).scalar_sum()
, but that would allocate a new array for the result of themapv
.)
Note that both l1_norm
⮳ and l2_norm
⮳ take the ndarray::ArrayView1
⮳ type. This recipe considers vector norms, so the norm functions only need to accept one-dimensional views, hence ndarray::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).
ndarray::Array
⮳ and ndarray::Array
⮳ are both type aliases for ndarray::Array
⮳. 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::Array1; use ndarray::ArrayView1; use ndarray::array; fn l1_norm(x: ArrayView1<f64>) -> f64 { x.fold(0., |acc, elem| acc + elem.abs()) } fn l2_norm(x: ArrayView1<f64>) -> f64 { x.dot(&x).sqrt() } fn normalize(mut x: Array1<f64>) -> Array1<f64> { let norm = l2_norm(x.view()); x.mapv_inplace(|e| e / norm); x } 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 a matrix
Creates a 3x3 matrix with nalgebra::Matrix3
⮳ and inverts it, if possible.
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
Serialize and deserialize a matrix to and from JSON. Serialization is taken care of by serde_json::to_string
⮳ and serde_json::to_string
⮳ performs deserialization.
Note that serialization followed by deserialization gives back the original matrix.
use nalgebra::DMatrix; fn main() -> Result<(), std::io::Error> { let row_slice: Vec<i32> = (1..5001).collect(); let matrix = DMatrix::from_row_slice(50, 100, &row_slice); println!("{}", matrix); // serialize matrix let serialized_matrix = serde_json::to_string(&matrix)?; // deserialize matrix let deserialized_matrix: DMatrix<i32> = serde_json::from_str(&serialized_matrix)?; // verify that `deserialized_matrix` is equal to `matrix` assert!(deserialized_matrix == matrix); Ok(()) }
General-purpose linear algebra library with transformations and statically-sized or dynamically-sized matrices. However it supports only vectors (1d) and matrices (2d) and not higher-dimensional tensors.
Less featureful than nalgebra but supports arbitrarily dimensioned arrays
Trigonometry
Recipe | Crates | Categories |
---|---|---|
Calculate the side length of a triangle | ||
Verify that tan is equal to sin divided by cos | ||
Calculate the distance between two points on Earth |
Calculate the side length of a triangle
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; let hypotenuse = side_length / angle.sin(); println!("Hypotenuse: {}", hypotenuse); }
Verify that tan
is equal to sin
divided by cos
Verifies tan(x)
is equal to sin(x)/cos(x)
for x = 6.
fn main() { let x: f64 = 6.0; let a = x.tan(); let b = x.sin() / x.cos(); println!("a: {a}, b: {b}"); assert_eq!(a, b); }
Calculate the distance between two points on Earth
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 radians. 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); let paris_latitude = paris_latitude_degrees.to_radians(); let london_latitude = london_latitude_degrees.to_radians(); let delta_latitude = (paris_latitude_degrees - london_latitude_degrees).to_radians(); let delta_longitude = (paris_longitude_degrees - london_longitude_degrees).to_radians(); let central_angle_inner = (delta_latitude / 2.0).sin().powi(2) + paris_latitude.cos() * london_latitude.cos() * (delta_longitude / 2.0).sin().powi(2); let central_angle = 2.0 * central_angle_inner.sqrt().asin(); let distance = earth_radius_kilometer * central_angle; println!( "Distance between Paris and London on the surface of Earth is {:.1} kilometers", distance ); }
Complex numbers
Recipe | Crates | Categories |
---|---|---|
Create complex numbers | ||
Add complex numbers | ||
Use mathematical functions on complex numbers |
Create 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); println!("Complex integer: {}", complex_integer); println!("Complex float: {}", complex_float); }
Add complex numbers
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); let sum = complex_num1 + complex_num2; println!("Sum: {}", sum); }
Use mathematical functions on complex numbers
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); println!("e^(2i * pi) = {}", x.exp()); // =~1 }
Statistics
Recipe | Crates | Categories |
---|---|---|
Calculate measures of central tendency | ||
Compute the standard deviation |
Calculate measures of central tendency
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 std::option::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 std::iter::Iterator::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]; let sum = data.iter().sum::<i32>() as f32; let count = data.len(); let mean = match count { positive if positive > 0 => Some(sum / count as f32), _ => None, }; println!("Mean of the data is {:?}", mean); }
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 std::cmp::Ord::cmp
⮳ and std::cmp::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; fn partition(data: &[i32]) -> Option<(Vec<i32>, i32, Vec<i32>)> { match data.len() { 0 => None, _ => { let (pivot_slice, tail) = data.split_at(1); let pivot = pivot_slice[0]; let (left, right) = tail.iter().fold((vec![], vec![]), |mut splits, next| { { let &mut (ref mut left, ref mut right) = &mut splits; if next < &pivot { left.push(*next); } else { right.push(*next); } } splits }); Some((left, pivot, right)) } } } fn select(data: &[i32], k: usize) -> Option<i32> { let part = partition(data); 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)), } } } } fn median(data: &[i32]) -> Option<f32> { let size = data.len(); match size { even if even % 2 == 0 => { let fst_med = select(data, (even / 2) - 1); let snd_med = select(data, even / 2); match (fst_med, snd_med) { (Some(fst), Some(snd)) => Some((fst + snd) as f32 / 2.0), _ => None, } } odd => select(data, odd / 2).map(|x| x as f32), } } fn main() { let data = [3, 1, 6, 1, 5, 8, 1, 8, 10, 11]; let part = partition(&data); println!("Partition is {:?}", part); let sel = select(&data, 5); println!("Selection at ordered index {} is {:?}", 5, sel); let med = median(&data); println!("Median is {:?}", med); }
The final example calculates the mode using a mutable std::collections::HashMap
⮳ to collect counts of each distinct integer from the set, using a std::iter::Iterator::fold
⮳ and the std::collections::hash_map::Entry
⮳ API. The most frequent value in the std::collections::HashMap
⮳ surfaces with std::iter::Iterator::max_by_key
⮳.
use std::collections::HashMap; fn main() { let data = [3, 1, 6, 1, 5, 8, 1, 8, 10, 11]; let frequencies = data.iter().fold(HashMap::new(), |mut freqs, value| { *freqs.entry(value).or_insert(0) += 1; freqs }); let mode = frequencies .into_iter() .max_by_key(|&(_, count)| count) .map(|(value, _)| *value); println!("Mode of the data is {:?}", mode); }
Compute the 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 std::iter::Iterator::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, } } fn std_deviation(data: &[i32]) -> Option<f32> { match (mean(data), data.len()) { (Some(data_mean), count) if count > 0 => { let variance = data .iter() .map(|value| { let diff = data_mean - (*value as f32); 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]; let data_mean = mean(&data); println!("Mean is {:?}", data_mean); let data_std_deviation = std_deviation(&data); println!("Standard deviation is {:?}", data_std_deviation); let zscore = match (data_mean, data_std_deviation) { (Some(mean), Some(std_deviation)) => { let diff = data[4] as f32 - mean; Some(diff / std_deviation) } _ => None, }; println!( "Z-score of data at index 4 (with value {}) is {:?}", data[4], zscore ); }
Additional numeric types
Recipe | Crates | Categories |
---|---|---|
Abstract over different number types | ||
Use big integers | ||
Use big decimals | ||
Sort floats |
| num-bigint
| | |
| num
| | |
| rug
| | |
Abstract over different number types
Numeric traits for generic mathematics. Traits like Number, Add, etc that allow you write functions that are generic over the specific numeric type
fn main() { todo!(); }
Use big integers
num
A collection of numeric types and traits for Rust, including bigint, complex, rational, range iterators, generic integers, and more! Calculation for integers exceeding 128 bits are possible with num::BigInt
⮳.
use num::bigint::BigInt; use num::bigint::ToBigInt; fn factorial(x: i32) -> BigInt { if let Some(mut factorial) = 1.to_bigint() { for i in 1..=x { factorial *= i; } factorial } else { panic!("Failed to calculate factorial!"); } } fn main() { println!("{}! equals {}", 100, factorial(100)); }
num-bigint
Big integer implementation for Rust. "It's not the fastest, but it's part of the trusted num library."
fn main() { todo!(); }
rug
Arbitrary-precision integers, rational, floating-point and complex numbers based on GMP, MPFR and MPC. LGPL licensed. Wrapper for GMP. Much faster than num-bigint
.
fn main() { todo!(); }
Use big decimals
Decimal number implementation written in pure Rust suitable for financial and fixed-precision calculations. The binary representation consists of a 96 bit integer number, a scaling factor used to specify the decimal fraction and a 1 bit sign.
fn main() { todo!(); }
Sort floats
Wrappers for total ordering on floats. Float types that don't allow NaN
and are therefore orderable. You can also use the total_cmp
method from the standard library like .sort_by(|a, b| a.total_cmp(&b))
.
fn main() { todo!(); }
Memory Management
Deal with allocation, memory mapping, garbage collection, reference counting, or interfaces to foreign memory managers.
Recipe | Crates | Categories |
---|---|---|
Declare lazily evaluated constants |
Recipe | Crates | Categories |
---|---|---|
std | ||
once_cell | ||
lazy_static |
Global static
Recipe | Crates | Categories |
---|---|---|
Declare lazily evaluated constants |
Declare lazily evaluated constants
Declares a lazily evaluated constant std::collections::HashMap
⮳. The std::collections::HashMap
⮳ will be evaluated once and stored behind a global static reference.
use std::collections::HashMap; use lazy_static::lazy_static; 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"); }
Lazy Initialization
Recipe | Crates | Categories |
---|---|---|
std | ||
once_cell | ||
lazy_static |
Two key libraries:
once_cell
: newer crate with more ergonomic API. Should be preferred for all new projects.lazy_static
: older crate. API is less convenient, but crate is stable and maintained.
The core functionality of once_cell
is now included in the standard library with the remaining parts on track to be stabilised in future.
std
OnceCell⮳ is a cell which can be written to only once.
The corresponding Sync version of OnceCell<T>
is OnceLock<T>
.
use std::cell::OnceCell; fn main() { let cell = OnceCell::new(); assert!(cell.get().is_none()); let value: &String = cell.get_or_init(|| "Hello, World!".to_string()); println!("{value}"); assert_eq!(value, "Hello, World!"); assert!(cell.get().is_some()); }
once_cell
once_cell
⮳ provides two cell-like types, unsync::OnceCell
and sync::OnceCell
. A OnceCell
might store arbitrary non-Copy types, can be assigned to at most once and provides direct access to the stored contents. The sync
flavor is thread-safe. once_cell
also has a once_cell::sync::Lazy
⮳ type, build on top of OnceCell
⮳:
use std::collections::HashMap; use std::sync::Mutex; use once_cell::sync::Lazy; // Must be static, not const static GLOBAL_DATA: Lazy<Mutex<HashMap<i32, String>>> = Lazy::new(|| { let mut m = HashMap::new(); m.insert(13, "Spica".to_string()); m.insert(74, "Hoyten".to_string()); Mutex::new(m) }); fn main() { println!("{:?}", GLOBAL_DATA.lock().unwrap()); }
lazy_static
fn main() { todo!(); }
Operating System
Bindings to operating system-specific APIs.
External
Low-level system calls
Recipe | Crates | Categories |
---|---|---|
Call libc , the C standard library |
Operating Systems written in Rust
Recipe | Crates | Categories |
---|---|---|
Host containers with bottlerocket | ||
Run a Rust operating system on your computer with Redox |
External Command
Run an external command and process its stdout
Runs git log --oneline
as an external std::process::Command
⮳ and inspects its std::process::Output
⮳ using regex::Regex
⮳ to get the hash and message of the last 5 commits.
use std::process::Command; use anyhow::Result; use anyhow::bail; use regex::Regex; #[derive(PartialEq, Default, Clone, Debug)] struct Commit { hash: String, message: String, } fn main() -> Result<()> { let output = Command::new("git").arg("log").arg("--oneline").output()?; if !output.status.success() { bail!("Command executed with failing error code"); } let pattern = Regex::new( r"(?x) ([0-9a-fA-F]+) # commit hash (.*) # The commit message", )?; 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(()) }
Run an external command, passing it stdin
, then check for an error code
Opens the python
interpreter using an external std::process::Command
⮳ and passes it a python statement for execution. The std::process::Output
⮳ of statement is then parsed.
use std::collections::HashSet; use std::io::Write; use std::process::Command; use std::process::Stdio; use anyhow::Result; use anyhow::anyhow; use anyhow::bail; fn main() -> Result<()> { let mut child = Command::new("rev") .stdin(Stdio::piped()) .stderr(Stdio::piped()) .stdout(Stdio::piped()) .spawn()?; child .stdin .as_mut() .ok_or(anyhow!("Child process' stdin has not been captured!"))? .write_all(b"1234 56789")?; let output = child.wait_with_output()?; 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); } else { let err = String::from_utf8(output.stderr)?; bail!("External command failed:\n {}", err); } Ok(()) }
Run piped external commands
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
.
std::process::Command
⮳ represent a process. Output of a child process is captured with a std::process::Stdio::piped
⮳ between parent and child.
#![allow(clippy::single_match)] #![cfg(target_family = "unix")] fn main() -> anyhow::Result<()> { use std::process::Command; use std::process::Stdio; let directory = std::env::current_dir()?; let mut du_output_child = Command::new("du") .arg("-ah") .arg(&directory) .stdout(Stdio::piped()) .spawn()?; if let Some(du_output) = du_output_child.stdout.take() { let mut sort_output_child = Command::new("sort") .arg("-hr") .stdin(du_output) .stdout(Stdio::piped()) .spawn()?; du_output_child.wait()?; match sort_output_child.stdout.take() { Some(sort_output) => { let head_output_child = Command::new("head") .args(["-n", "10"]) .stdin(sort_output) .stdout(Stdio::piped()) .spawn()?; let head_stdout = head_output_child.wait_with_output()?; 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 the stdout
and stderr
of a child process to the same file
Spawns a child process and redirects std::io::Stdout
⮳ and std::io::Stderr
⮳ to the same file. It follows the same idea as run piped external commands, however std::process::Stdio
⮳ writes to a specified file. std::fs::File::try_clone
⮳ references the same file handle for std::io::Stdout
⮳ and std::io::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
.
fn main() -> Result<(), std::io::Error> { use std::fs; use std::fs::File; use std::process::Command; use std::process::Stdio; if !fs::exists("temp")? { fs::create_dir("temp")?; } let outputs = File::create("temp/out.txt")?; let errors = outputs.try_clone()?; Command::new("ls") .args([".", "oops"]) .stdout(Stdio::from(outputs)) .stderr(Stdio::from(errors)) .spawn()? .wait_with_output()?; Ok(()) }
Continuously process the outputs of a child process
In Run an external command and process its stdout
, processing doesn't start until the external std::process::Command
is finished. The recipe below calls std::process::Stdio::piped
to create a pipe, and reads
std::io::Stdout
⮳ continuously as soon as the std::io::BufReader
⮳ is updated.
The below recipe is equivalent to the Unix shell command journalctl | grep usb
.
use std::io::BufRead; use std::io::BufReader; use std::io::Error; use std::io::ErrorKind; use std::process::Command; use std::process::Stdio; fn main() -> Result<(), Error> { // NOTE: `systemd` should be installed for this example to work. let stdout = Command::new("journalctl") .stdout(Stdio::piped()) .spawn()? .stdout .ok_or_else(|| { Error::new(ErrorKind::Other, "Could not capture standard output.") })?; let reader = BufReader::new(stdout); reader .lines() .map_while(Result::ok) .filter(|line| line.contains("usb")) .for_each(|line| println!("{}", line)); Ok(()) }
Read an environment variable
Reads an environment variable via std::env::var
⮳.
use std::env; use std::fs; use std::io::Error; fn main() -> Result<(), Error> { // Read `config_path` from the environment variable `CONFIG`. // If `CONFIG` isn't set, fall back to a default config path. let config_path = env::var("CONFIG").unwrap_or("/etc/subversion/config".to_string()); let config: String = fs::read_to_string(config_path)?; println!("Config: {}", config); Ok(()) }
Run child processes using duct
duct
⮳ is a library for running child processes. duct
makes it easy to build pipelines and redirect I/O like a shell. At the same time, duct
helps you write correct, portable code: whitespace is never significant, errors from child processes get reported by default, and a variety of gotchas, bugs, and platform inconsistencies⮳ are handled for you.
Low-level interaction with the operating system
Recipe | Crates | Categories |
---|---|---|
Call libc , the C standard library |
Call libc
, the C standard library
Bindings for directly calling libc
functions.
fn main() { todo!(); }
Operating systems written in Rust
Recipe | Crates | Categories |
---|---|---|
Host containers with bottlerocket | ||
Run a Rust operating system on your computer with Redox |
Host containers with bottlerocket
bottlerocket⮳ is an operating system designed for hosting containers.
Run a Rust operating system on your computer with Redox
Redox⮳(github)⮳ is an experimental, Unix-like, general-purpose, microkernel-based operating system written in Rust, aiming to bring Rust to a modern microkernel, a full set of programs, and be a complete alternative to Linux and BSD.
Rust Patterns
Shared solutions for particular situations specific to programming in Rust.
Handle errors
Customize errors
Recipe | Crates | Categories |
---|---|---|
anyhow | ||
thisError | ||
miette | ||
color-eyre |
Rust design patterns
Recipe | Crates | Categories |
---|---|---|
Implement an abstract factory | {{#crate }} | |
Clone a struct storing a boxed trait object |
| Implement the typestate pattern in Rust | {{#crate }} | |
Functional programming
Recipe | Crates | Categories |
---|---|---|
Compose iterators |
Rust idioms
Recipe | Crates | Categories |
---|---|---|
Rust idioms and patterns | {{#crate }} |
Design Patterns
Recipe | Crates | Categories |
---|---|---|
Implement an abstract factory | {{#crate }} | |
Clone a struct storing a boxed trait object |
| Implement the typestate pattern in Rust | {{#crate }} | |
Implement an abstract factory
Clone a struct storing a boxed trait object
The dyn-clone
crate provides a DynClone
trait that can be used in trait objects, and a clone_box
function that can clone any sized or dynamically sized implementation of DynClone
. Types that implement the standard library’s std::clone::Clone
trait are automatically usable by a DynClone trait object.
use dyn_clone::DynClone; trait MyTrait: DynClone { fn recite(&self); } impl MyTrait for String { fn recite(&self) { println!("{} ♫", self); } } fn main() { let line = "The slithy structs did gyre and gimble the namespace"; // Build a trait object holding a String. // This requires String to implement MyTrait and std::clone::Clone. let x: Box<dyn MyTrait> = Box::new(String::from(line)); x.recite(); // The type of x2 is a Box<dyn MyTrait> cloned from x. let x2 = dyn_clone::clone_box(&*x); x2.recite(); }
Implement the typestate pattern in Rust
The typestate pattern is an API design pattern that encodes information about an object’s run-time state in its compile-time type. typestate pattern
Functional programming
Recipe | Crates | Categories |
---|---|---|
Compose iterators |
Compose iterators
itertools
includes extra iterator adaptors, functions and macros.
fn main() { use itertools::Itertools; use itertools::assert_equal; use itertools::chain; // Assert that two iterables produce equal sequences assert_equal("hello world".split(' '), "hello world".split(' ')); // Chain let mut result: Vec<i32> = Vec::new(); for element in chain(&[1, 2, 3], &[4]) { result.push(*element); } assert_eq!(result, vec![1, 2, 3, 4]); // Cloned assert_eq!(itertools::cloned(b"abc").next(), Some(b'a')); // Deduplicate let data = vec![1., 1., 2., 3., 3., 2., 2.]; itertools::assert_equal(data.into_iter().dedup(), vec![1., 2., 3., 2.]); // `into_group_map` let data = vec![(0, 10), (2, 12), (3, 13), (0, 20), (3, 33), (2, 42)]; let lookup = data.into_iter().into_group_map(); assert_eq!(lookup[&0], vec![10, 20]); assert_eq!(lookup.get(&1), None); assert_eq!(lookup[&2], vec![12, 42]); assert_eq!(lookup[&3], vec![13, 33]) }
functional_programming: review - lens is not used that often (P1)
Use lens
This Rust library provides support for lenses, which are a mechanism in functional programming for focusing on a part of a complex data structure.
fn main() { todo!(); }
Rust idioms and patterns
Recipe | Crates | Categories |
---|---|---|
Rust idioms and patterns | {{#crate }} |
See also
Builder derive crates
Recipe | Crates | Categories |
---|---|---|
bon | ||
derive_builder | ||
typed-builder |
typed-builder
Compile-time type-checked builder derive. The oldest crate for compile-time-checked builders that has (987K downloads/month, 916 stars, 7 years old)
derive_builder
Rust macro to automatically implement the builder pattern for arbitrary structs.
runtime-checked builders, works with &self, &mut self builder patterns. The oldest crate for runtime-checked builders overall (1,58M downloads/month, 1285 stars, 8 years old)
bon
Next-gen compile-time-checked builder generator, named function arguments.
bon - compile-time-checked builders, named function arguments via builders (foo().arg(...).call()), fallible/async builders, method builders (self.foo(...).arg(...).call()). The newest crate built based on lessons learned from typed-builder and derive_builder (33K downloads/month, but gaining popularity, 1095 stars, 3 months old).
Template Engine
Crates designed to combine templates with data to produce result documents, usually with an emphasis on processing text.
Create HTML files from a template
Recipe | Crates | Categories |
---|---|---|
Create HTML files from a template |
Create Markdown fragments from a template
Recipe | Crates | Categories |
---|---|---|
Create Markdown fragments from a template |
Tera
Recipe | Crates | Categories |
---|---|---|
Create HTML files from a template |
Create HTML files from a template
tera
is a template engine based on Jinja2/Django templates.
fn main() { todo!(); }
Tinytemplate
Recipe | Crates | Categories |
---|---|---|
Create Markdown fragments from a template |
Create Markdown fragments from a template
tinytemplate
is a simple, lightweight template engine.
fn main() { todo!(); }
Text Editors
Applications for editing text.
Recipe | Crates | Categories |
---|---|---|
Write Rust code with VS Code | VS Code⮳ | |
Write Rust code with the zed editor |
| Use the Helix Editor | Helix editor⮳ | |
| Write Rust code with RustRover | RustRover
⮳ | |
| Use neovim
| Neovim⮳ | |
IDEs
Recipe | Crates | Categories |
---|---|---|
Write Rust code with VS Code | VS Code⮳ | |
Write Rust code with the zed editor |
| Use the Helix Editor | Helix editor⮳ | |
| Write Rust code with RustRover | RustRover
⮳ | |
| Use neovim
| Neovim⮳ | |
VS Code⮳ | Install the rust-analyzer extension. Also consider the CodeLLDB , Dependi and Even Better TOML |
RustRover ⮳ | It is available for free for non-commercial use. |
IntelliJ / CLion JetBrains IDE + Rust plugin | If you have a JetBrains license, CLion is your go-to editor for Rust in JetBrains’ IDE suite |
Zed⮳ | zed is available for macOS, Linux, and soon for Windows. Written in Rust. |
Helix editor⮳(github)⮳ | Install the rust-analyzer binary⮳ |
vi , vim , or Neovim⮳ | Configure Vim for Rust⮳ |
Emacs⮳ (or derivatives like Doom Emacs, Spacemacs⮳, etc.) | Configure emacs for Rust⮳ |
Sublime Text⮳ | "Rust enhanced" package⮳ |
Visual Studio⮳ | Configure rust-analyzer for Visual Studio⮳ |
Lapce⮳ | Open source, written in Rust |
Xcode⮳ | |
Eclipse Corrosion⮳ | It provides development tools for Rust and Cargo inside the Eclipse IDE |
Atom⮳ | It has been sunset |
Write Rust code with VS Code
Write Rust code with the zed
editor
See also
Text Processing
Deal with the complexities of human language when expressed in textual form.
Find, extract, and replace text using regular expressions
| Use regular expressions with backreferences and lookarounds | | |
Parse strings
Recipe | Crates | Categories |
---|---|---|
Collect unicode graphemes | ||
Implement the FromStr trait for a custom struct |
Concatenate strings
Recipe | Crates | Categories |
---|---|---|
Compare string concatenation methods |
Regular Expressions
| Use regular expressions with backreferences and lookarounds | | |
Verify and extract login from an email address
Validates that an email address is formatted correctly, and extracts everything before the @
symbol.
use lazy_static::lazy_static; use regex::Regex; fn extract_login(input: &str) -> Option<&str> { lazy_static! { static ref RE: Regex = Regex::new( r"(?x) ^(?P<login>[^@\s]+)@ ([[:word:]]+\.)* [[:word:]]+$ " ) .unwrap(); } RE.captures(input) .and_then(|cap| cap.name("login").map(|login| login.as_str())) } fn main() { let login = extract_login(r"I❤email@example.com"); println!("{:?}", login); assert_eq!(login, Some(r"I❤email")); let login = extract_login(r"sdf+sdsfsd.as.sdsd@jhkk.d.rl"); println!("{:?}", login); assert_eq!(login, 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); }
Extract a list of unique #hashtags from a text
Extracts, sorts, and deduplicates list of hashtags from text.
The hashtag regex given here only catches Latin hashtags that start with a letter. The complete Twitter hashtag regex⮳ is much more complicated.
use std::collections::HashSet; use lazy_static::lazy_static; use regex::Regex; fn extract_hashtags(text: &str) -> HashSet<&str> { lazy_static! { static ref HASHTAG_REGEX: Regex = Regex::new(r"\#[a-zA-Z][0-9a-zA-Z_]*").unwrap(); } HASHTAG_REGEX .find_iter(text) .map(|mat| mat.as_str()) .collect() } fn main() { let tweet = "Hey #world, I just got my new #dog, say hello to Till. #dog #forever #2 #_ "; let tags = extract_hashtags(tweet); println!("{:?}", tags); assert!( tags.contains("#dog") && tags.contains("#forever") && tags.contains("#world") ); assert_eq!(tags.len(), 3); }
Extract phone numbers from text
Processes a string of text using regex::Regex::captures_iter
⮳ to capture multiple phone numbers. The example here is for US convention phone numbers.
use std::fmt; use anyhow::Result; use regex::Regex; struct PhoneNumber<'a> { area: &'a str, exchange: &'a str, subscriber: &'a str, } impl fmt::Display for PhoneNumber<'_> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "1 ({}) {}-{}", self.area, self.exchange, self.subscriber) } } fn main() -> Result<()> { let phone_text = " +1 505 881 9292 (v) +1 505 778 2212 (c) +1 505 881 9297 (f) (202) 991 9534 Alex 5553920011 1 (800) 233-2010 1.299.339.1020"; 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"#, )?; let phone_numbers = re.captures_iter(phone_text).filter_map(|cap| { let groups = (cap.get(2).or(cap.get(3)), cap.get(4), cap.get(5)); match groups { (Some(area), Some(ext), Some(sub)) => Some(PhoneNumber { area: area.as_str(), exchange: ext.as_str(), subscriber: sub.as_str(), }), _ => None, } }); 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(()) }
Filter a log file by matching multiple regular expressions
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.
A regex::RegexSetBuilder
⮳ composes a regex::RegexSetBuilder
⮳ Since backslashes are very common in regular expressions, using raw string literals⮳ makes them more readable.
use std::fs; use std::fs::File; use std::io::BufRead; use std::io::BufReader; use anyhow::Result; use regex::RegexSetBuilder; fn main() -> Result<()> { let log_path = "temp/application.log"; let buffered = BufReader::new(File::open(log_path)?); let set = RegexSetBuilder::new([ r#"version "\d\.\d\.\d""#, r#"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:443"#, r#"warning.*timeout expired"#, ]) .case_insensitive(true) .build()?; buffered .lines() // yield instances of io::Result<String> .map_while(Result::ok) .filter(|line| set.is_match(line.as_str())) .for_each(|x| println!("{}", x)); Ok(()) }
Replace all occurrences of one text pattern with another pattern
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::Regex::replace_all
⮳ replaces all occurrences of the whole regex.
&str
implements the regex::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 std::borrow::Cow; use lazy_static::lazy_static; use regex::Regex; fn reformat_dates(before: &str) -> Cow<str> { lazy_static! { static ref ISO8601_DATE_REGEX: Regex = Regex::new(r"(?P<y>\d{4})-(?P<m>\d{2})-(?P<d>\d{2})").unwrap(); } ISO8601_DATE_REGEX.replace_all(before, "$m/$d/$y") } fn main() { let before = "2012-03-14, 2013-01-15 and 2014-07-05"; let after = reformat_dates(before); println!("{}", after); assert_eq!(after, "03/14/2012, 01/15/2013 and 07/05/2014"); }
Use regular expressions with backreferences and lookarounds
regex
is the de facto standard regex library. It is very fast, but does not support fancier features such as backtracking, backreferences, and look-arounds. Use fancy-regex
if you need features that regex
doesn't support.
fn main() { todo!(); }
Longer Regex Example
| Use regular expressions with backreferences and lookarounds | | |
use std::collections::BTreeMap; use once_cell::sync::Lazy; use regex::Regex; // Regular expression and the names of its capture groups. struct Re(Regex, Vec<&'static str>); // Regexes take a while to compile; it is reasonable to store them in // a global static static GLOBAL_REGEX: Lazy<BTreeMap<&str, Re>> = Lazy::new(|| { println!("Initializing Regexes...\n"); // A sorted map: let mut m = BTreeMap::new(); // A Markdown inline link: // (?<name> ) is a named capture group. // \s is a whitespace. \S is a not-whitespace. m.insert( "[text](http...)", Re( Regex::new(r"[^!]\[(?<text>.*?)\]\s?\(\s*?(?<link>\S*?)\s*?\)") .unwrap(), vec!["text", "link"], ), ); // A Markdown autolink m.insert( "<http...>", Re(Regex::new(r"<(?<link>http.*?)>").unwrap(), vec!["link"]), ); // A Markdown shortcut link // or <spaces>( or : m.insert( "[text] ...", Re( Regex::new(r"[^!\]]\[(?<text>[^\[\]]+?)\]\s*?[^\[\(:]").unwrap(), vec!["text"], ), ); // A Markdown reference-style link m.insert( "[text][label]", Re( Regex::new(r"[^!\]]\[(?<text>.*?)\]\s?\[(?<label>.+?)\]").unwrap(), vec!["text", "label"], ), ); // A Markdown reference definition (with optional title): // (?: ) is a non-capturing group. // (?m) flags multi-line mode. ^ and $ are the beginning and end of a // line, respectively. m.insert( "[label]: url \"title\"", Re(Regex::new(r#"(?m)^\s*?\[(?<label>.*?)\]:\s*?(?<url>\S+)\s*?(?:"(?<title>.*)")?\s*$"#).unwrap(), vec!["label", "url", "title"]) ); m }); #[allow(dead_code)] fn extract_inline_links(contents: &str) { for (_, [text, link]) in GLOBAL_REGEX["[text](http...)"] .0 // `captures_iter` iterates through `Captures`, which stores the // capture groups for each match. .captures_iter(contents) // `extract` returns a tuple where // the first element corresponds to the full substring of the contents // that matched the regex. The second element is an array of // substrings, with each corresponding to the substring that matched // for a particular capture group. .map(|c| c.extract()) { println!("[{text}]({link})\n"); } } // Locate markup in text fn search_with_all_regexes(contents: &str) { // Try to match all reggular expressions for (key, re) in GLOBAL_REGEX.iter() { println!("----------------------\nLooking for {}:\n", key); for caps in re.0.captures_iter(contents) { // Print the whole match print!("{} -> ", &caps[0]); for group in re.1.iter() { print!( "{}={}; ", group, // Retrieve each named capture group in turn... // `extract` can't be used here, since the # of capture // groups varies. caps.name(group).map_or("", |m| m.as_str()) ); } println!("\n"); } } } // Example Markdown to process fn get_test_markdown() -> String { let md: &'static str = " <http://url0/> [text1](url1) [text2][lbl2] [lbl2]: url2 \"title2\" [lbl3][] [lbl4] ![image5](image_url5) ![image6][image_lbl6] image_lbl6: image_url6 ![image_lbl7] ![image_lbl8][] "; md.to_owned() } fn main() { search_with_all_regexes(get_test_markdown().as_str()); }
String Parsing
Recipe | Crates | Categories |
---|---|---|
Collect unicode graphemes | ||
Implement the FromStr trait for a custom struct |
Collect unicode graphemes
Collect individual Unicode graphemes from UTF-8 string using the unicode_segmentation::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>>(); println!("{:?}", graphemes); assert_eq!(graphemes[3], "é"); }
Implement the FromStr
trait for a custom struct
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, } impl FromStr for Rgb { type Err = std::num::ParseIntError; // Parses a color hex code of the form '#rRgGbB..' into an // instance of 'RGB' fn from_str(hex_code: &str) -> Result<Self, Self::Err> { // u8::from_str_radix(src: &str, radix: u32) converts a string // slice in a given base to u8 let r: u8 = u8::from_str_radix(&hex_code[1..3], 16)?; let g: u8 = u8::from_str_radix(&hex_code[3..5], 16)?; let b: u8 = u8::from_str_radix(&hex_code[5..7], 16)?; 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); } } // test whether from_str performs as expected assert_eq!(Rgb::from_str(r"#fa7268").unwrap(), Rgb { r: 250, g: 114, b: 104 }); }
String concatenation
Recipe | Crates | Categories |
---|---|---|
Compare string concatenation methods |
Compare string concatenation methods
String concatenation benchmark⮳
fn main() { todo!(); }
Web Programming
Create applications for the Web.
Manipulate uniform resource locations (URLs)
Handle media types (MIME)
Recipe | Crates | Categories |
---|---|---|
Get a MIME type from a string | ||
Get a MIME type from a filename | ||
Parse the MIME type of a HTTP response |
Scrape Web Pages
Recipe | Crates | Categories |
---|---|---|
Extract all links from the HTML of a webpage | ||
Check a webpage for broken links | ||
Extract all unique links from a MediaWiki markup |
See also
Building a crawler in Rust: design and associated types⮳
HTTP types & interfaces
The http
crate doesn't actually contain an HTTP implementation. Just types and interfaces to help interoperability.
Extracting Links
Recipe | Crates | Categories |
---|---|---|
Extract all links from the HTML of a webpage | ||
Check a webpage for broken links | ||
Extract all unique links from a MediaWiki markup |
Extract all links from the HTML of a webpage
Use reqwest::get
⮳ to perform a HTTP GET request and then use select::document::Document::from_read
⮳ to parse the response into a HTML document. select::document::Document::find
⮳ with the criteria of select::predicate::Name
⮳ is "a" retrieves all links. Call std-core::iter::Iterator::filter_map
⮳ on the select::selection::Selection
⮳ retrieves URLs from links that have the "href" select::node::Node::attr
⮳ (attribute).
use anyhow::Result; use select::document::Document; use select::predicate::Name; #[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(()) }
Check a webpage for broken links
Call get_base_url
to retrieve the base URL. If the document has a base tag, get the href select::node::Node::attr
⮳ from base tag. select::node::Node::attr
⮳ of the original URL acts as a default.
Iterates through links in the document and creates a tokio::task::spawn
⮳ task that will parse an individual link with url::ParseOptions
⮳ and tokio::task::spawn
⮳. The task makes a request to the links with reqwest
⮳ and verifies
reqwest::StatusCode
⮳. Then the tasks await
⮳ completion before ending the program.
use std::collections::HashSet; use anyhow::Result; use reqwest::StatusCode; use select::document::Document; use select::predicate::Name; use url::Position; use url::Url; async fn get_base_url(url: &Url, doc: &Document) -> Result<Url> { let base_tag_href = doc.find(Name("base")).filter_map(|n| n.attr("href")).next(); let base_url = base_tag_href .map_or_else(|| Url::parse(&url[..Position::BeforePath]), Url::parse)?; Ok(base_url) } async fn check_link(url: &Url) -> Result<bool> { let res = reqwest::get(url.as_ref()).await?; Ok(res.status() != StatusCode::NOT_FOUND) } #[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![]; for link in links { tasks.push(tokio::spawn(async move { if check_link(&link).await.unwrap() { println!("{} is OK", link); } else { println!("{} is Broken", link); } })); } for task in tasks { task.await? } Ok(()) }
Extract all unique links from a MediaWiki markup
Pull the source of a MediaWiki page using reqwest::get
⮳ and then look for all entries of internal and external links with regex::Regex::captures_iter
⮳. Using std::borrow::Cow
⮳ avoids excessive std::string::String
⮳ allocations.
MediaWiki link syntax is described here⮳.
use std::borrow::Cow; use std::collections::HashSet; use anyhow::Result; use lazy_static::lazy_static; use regex::Regex; fn extract_links(content: &str) -> HashSet<Cow<str>> { lazy_static! { static ref WIKI_REGEX: Regex = Regex::new( r"(?x) \[\[(?P<internal>[^\[\]|]*)[^\[\]]*\]\] # internal links | (url=|URL\||\[)(?P<external>http.*?)[ \|}] # external links " ) .unwrap(); } let links: HashSet<_> = WIKI_REGEX .captures_iter(content) .map(|c| match (c.name("internal"), c.name("external")) { (Some(val), None) => Cow::from(val.as_str().to_lowercase()), (None, Some(val)) => Cow::from(val.as_str()), _ => unreachable!(), }) .collect(); links } #[tokio::main] async fn main() -> Result<()> { 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
Parse a URL from a string to a Url
type
The url::Url::parse
⮳ method from the url
⮳ crate validates and parses a &str
into a url::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::Url
⮳ type.
use url::ParseError; use url::Url; fn main() -> Result<(), ParseError> { let s = "https://github.com/rust-lang/rust/issues?labels=E-easy&state=open"; let parsed = Url::parse(s)?; println!("The path part of the URL is: {}", parsed.path()); Ok(()) }
Create a base URL by removing path segments
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. url::PathSegmentsMut::clear
⮳ removes paths and url::Url::set_query
⮳ removes query string.
use anyhow::Result; use anyhow::anyhow; use url::Url; fn base_url(mut url: Url) -> Result<Url> { match url.path_segments_mut() { Ok(mut path) => { path.clear(); } Err(_) => { return Err(anyhow!("This URL is cannot-be-a-base.")); } } url.set_query(None); Ok(url) } fn main() -> Result<()> { let full = "https://github.com/rust-lang/cargo?asdf"; let url = Url::parse(full)?; let base = base_url(url)?; assert_eq!(base.as_str(), "https://github.com/"); println!("The base of the URL is: {}", base); Ok(()) }
Create new URLs from a base URL
The url::Url::join
⮳ method creates a new URL from a base and relative path.
use url::ParseError; use url::Url; fn build_github_url(path: &str) -> Result<Url, ParseError> { const GITHUB: &str = "https://github.com"; let base = Url::parse(GITHUB).expect("This hardcoded URL is known to be valid"); let joined = base.join(path)?; Ok(joined) } fn main() -> Result<(), ParseError> { let path = "/rust-lang/cargo"; let gh = build_github_url(path)?; assert_eq!(gh.as_str(), "https://github.com/rust-lang/cargo"); println!("The joined URL is: {}", gh); Ok(()) }
Extract the URL origin (scheme / host / port)
The url::Url
⮳ struct exposes various methods to extract information about the URL it represents.
use url::Host; use url::ParseError; use url::Url; fn main() -> Result<(), ParseError> { let s = "ftp://rust-lang.org/examples"; let url = Url::parse(s)?; 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(()) }
url::Url::origin
⮳ produces the same result.
use anyhow::Result; use url::Host; use url::Origin; use url::Url; fn main() -> Result<()> { let s = "ftp://rust-lang.org/examples"; let url = Url::parse(s)?; let expected_scheme = "ftp".to_owned(); let expected_host = Host::Domain("rust-lang.org".to_owned()); let expected_port = 21; let expected = Origin::Tuple(expected_scheme, expected_host, expected_port); let origin = url.origin(); assert_eq!(origin, expected); println!("The origin is as expected!"); Ok(()) }
Remove fragment identifiers and query pairs from a URL
Parses url::Url
⮳ and slices it with url::Position
⮳ to strip unneeded URL parts.
use url::ParseError; use url::Position; use url::Url; fn main() -> Result<(), ParseError> { let parsed = Url::parse( "https://github.com/rust-lang/rust/issues?labels=E-easy&state=open", )?; let cleaned: &str = &parsed[..Position::AfterPath]; println!("`cleaned`: {}", cleaned); Ok(()) }
Media Types
Recipe | Crates | Categories |
---|---|---|
Get a MIME type from a string | ||
Get a MIME type from a filename | ||
Parse the MIME type of a HTTP response |
Get a MIME type from a string
The following example shows how to parse a mime::Mime
type from a string using the mime⮳ crate. mime::Mime
⮳ produces a default mime::Mime
⮳ type in an std::result::Result::unwrap_or
⮳ clause.
use mime::APPLICATION_OCTET_STREAM; use mime::Mime; 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 ); let valid_mime_type = "TEXT/PLAIN"; let parsed_mime = valid_mime_type .parse::<Mime>() .unwrap_or(APPLICATION_OCTET_STREAM); println!( "MIME for {:?} was parsed as {:?}", valid_mime_type, parsed_mime ); }
Get a MIME type from a filename
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 find_mimetype(filename: &str) -> Mime { let parts: Vec<&str> = filename.split('.').collect(); let res = match parts.last() { Some(v) => match *v { "png" => mime::IMAGE_PNG, "jpg" => mime::IMAGE_JPEG, "json" => mime::APPLICATION_JSON, &_ => mime::TEXT_PLAIN, }, None => mime::TEXT_PLAIN, }; res } fn main() { let filenames = vec!["foobar.jpg", "foo.bar", "foobar.png"]; for file in filenames { let mime = find_mimetype(file); println!("MIME for {}: {}", file, mime); } }
Parse the MIME type of a HTTP response
When receiving a HTTP response 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 std::str::FromStr; use anyhow::Result; use mime::Mime; use reqwest::header::CONTENT_TYPE; #[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", }; println!("The reponse contains {}.", media_type); } }; Ok(()) }
Clients
Make HTTP network requests.
HTTP client libraries
Make HTTP requests
Recipe | Crates | Categories |
---|---|---|
Make a HTTP GET request | ||
Make a HTTP GET request asynchronously | ||
Set custom headers and URL parameters for a REST request |
Call an API
Recipe | Crates | Categories |
---|---|---|
Query the GitHub API | ||
Check if an API resource exists | ||
Create and delete a Gist with the GitHub API | ||
Consume a paginated RESTful API | ||
Handle a rate-limited API |
Download and upload
Recipe | Crates | Categories |
---|---|---|
Download a file to a temporary directory | ||
Make a partial download with HTTP range headers | ||
POST a file to paste.rs |
HTTP clients
reqwest
reqwest
is a full-fat HTTP client. It can be used in both synchronous and asynchronous code. It requires the tokio
runtime.
fn main() { todo!(); }
ureq
ureq
is a minimal synchronous HTTP client, focused on simplicity and minimizing dependencies.
fn main() { todo!(); }
hyper
hyper
is a low-level HTTP implementation (both client and server). It implements HTTP/1, and HTTP/2. It works best with the tokio
async runtime, but can support other runtimes.
fn main() { todo!(); }
Make HTTP requests
Recipe | Crates | Categories |
---|---|---|
Make a HTTP GET request | ||
Make a HTTP GET request asynchronously | ||
Set custom headers and URL parameters for a REST request |
Make a HTTP GET request
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 std::string::String
⮳ using std::io::Read::read_to_string
⮳.
use std::io::Read; use anyhow::Result; fn main() -> Result<()> { let mut res = reqwest::blocking::get("http://httpbin.org/get")?; let mut body = String::new(); res.read_to_string(&mut body)?; println!("Status: {}", res.status()); println!("Headers:\n{:#?}", res.headers()); println!("Body:\n{}", body); Ok(()) }
Make a HTTP GET request asynchronously
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
.
Uses the asynchronous versions of reqwest
⮳, both reqwest::get
⮳ and
reqwest::Response
⮳.
use anyhow::Result; #[tokio::main] async fn main() -> Result<()> { let res = reqwest::get("http://httpbin.org/get").await?; println!("Status: {}", res.status()); println!("Headers:\n{:#?}", res.headers()); let body = res.text().await?; println!("Body:\n{}", body); Ok(()) }
Set custom headers and URL parameters for a REST request
Sets both standard and custom HTTP headers as well as URL parameters for a HTTP GET request. Creates a custom header of type XPoweredBy
with hyper::header!
⮳ macro.
Builds complex URL with url::Url::parse_with_params
⮳. Sets standard headers
hyper::header::USER_AGENT
⮳ hyper::header::AUTHORIZATION
⮳ and custom XPoweredBy
with reqwest::RequestBuilder::header
⮳, then makes the request with
reqwest::RequestBuilder::send
⮳.
The request targets http://httpbin.org/headers service which responds with a JSON dict containing all request headers for easy verification.
use std::collections::HashMap; use anyhow::Result; use reqwest::header; use serde::Deserialize; #[derive(Deserialize, Debug)] pub struct HeadersEcho { pub headers: HashMap<String, String>, } fn main() -> Result<()> { // Parse an absolute URL from a string and add params to its query string let url = url::Url::parse_with_params("http://httpbin.org/headers", &[ ("lang", "rust"), ("browser", "servo"), ])?; // Define default headers for all requests let mut default_headers = header::HeaderMap::new(); default_headers .insert("X-MY-HEADER", header::HeaderValue::from_static("value")); let client = reqwest::blocking::Client::builder() .user_agent("Rust-test") .default_headers(default_headers) .build()?; // Headers for this request only let mut headers = header::HeaderMap::new(); headers.insert( reqwest::header::CONTENT_TYPE, header::HeaderValue::from_static("image/png"), ); let response = client.get(url) .headers(headers) .bearer_auth("DEadBEEfc001cAFeEDEcafBAd") // Enable HTTP bearer authentication. .send()?; assert_eq!( response.url().as_str(), "http://httpbin.org/headers?lang=rust&browser=servo" ); let out: HeadersEcho = response.json()?; assert_eq!( out.headers["Authorization"], "Bearer DEadBEEfc001cAFeEDEcafBAd" ); assert_eq!(out.headers["User-Agent"], "Rust-test"); assert_eq!(out.headers["X-My-Header"], "value"); println!("{:?}", out); Ok(()) }
Call an API
Recipe | Crates | Categories |
---|---|---|
Query the GitHub API | ||
Check if an API resource exists | ||
Create and delete a Gist with the GitHub API | ||
Consume a paginated RESTful API | ||
Handle a rate-limited API |
Query the GitHub API
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 reqwest::Response::json
⮳ into User
objects implementing serde::Deserialize
⮳.
tokio::main
is used to set up the async executor and the process waits for reqwest::get
to complete before processing the response into User instances.
use reqwest::Error; use serde::Deserialize; #[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 = "john-cd", repo = "rust_howto" ); println!("{}", request_url); let client = reqwest::Client::builder().user_agent("Rust-test").build()?; let response = client.get(&request_url).send().await?; let users: Vec<User> = response.json().await?; println!("{:?}", users); Ok(()) }
Check if an API resource exists
Query the GitHub Users Endpoint using a HEAD request reqwest::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 reqwest::ClientBuilder::timeout
⮳ ensures a request will not last longer than a timeout.
Due to both reqwest::ClientBuilder::build
⮳ and reqwest::RequestBuilder::send
⮳ returning reqwest::Error
⮳ types, the shortcut reqwest::Result
⮳ is used for the main function return type.
use std::time::Duration; use reqwest::ClientBuilder; #[tokio::main] async fn main() -> reqwest::Result<()> { let user = "ferris-the-crab"; let request_url = format!("https://api.github.com/users/{}", user); println!("{}", request_url); let timeout = Duration::new(5, 0); let client = ClientBuilder::new().timeout(timeout).build()?; let response = client.head(&request_url).send().await?; if response.status().is_success() { println!("{} is a user!", user); } else { println!("{} is not a user!", user); } Ok(()) }
Create and delete a Gist with the GitHub API
Creates a gist with POST request to GitHub gists API v3⮳ using reqwest::Client::post
⮳ and removes it with DELETE request using reqwest::Client::post
⮳.
The reqwest::Client
⮳ is responsible for details of both requests including URL, body and authentication. The POST body from reqwest::Client
⮳ macro provides arbitrary JSON body. Call to reqwest::Client
⮳ sets the request body. reqwest::Client
⮳ handles authentication. The call to reqwest::Client
⮳ synchronously executes the requests.
use std::collections::HashMap; use std::env; use anyhow::Result; use reqwest::Client; use serde::Deserialize; use serde::Serialize; #[derive(Deserialize, Serialize, Debug)] struct Post<'a> { description: &'a str, public: bool, files: HashMap<&'a str, Content<'a>>, } #[derive(Deserialize, Serialize, Debug)] struct Content<'a> { content: &'a str, } #[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")?; // Example POST to the GitHub gists API let gist_body = Post { description: "the description for this gist", public: true, files: { let mut h = HashMap::new(); h.insert("main.rs", Content { content: r#" fn main() { println!("hello world!");} "#, }); h }, }; let request_url = "https://api.github.com/gists"; let response = Client::new() .post(request_url) .basic_auth(gh_user.clone(), Some(gh_pass.clone())) .json(&gist_body) .send() .await?; let gist: Gist = response.json().await?; println!("Created {:?}", gist); let request_url = format!("{}/{}", request_url, gist.id); let response = Client::new() .delete(&request_url) .basic_auth(gh_user, Some(gh_pass)) .send() .await?; println!( "Gist {} deleted! Status code: {}", gist.id, response.status() ); Ok(()) }
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
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.
// version_id then join version_id and Version.id ro retrieve crate // name Consider a simpler API for example purposes // use anyhow::Result; // use serde::Deserialize; // // Structs used to deserialize the JSON produced by the API // #[derive(Deserialize)] // struct ApiResponse { // dependencies: Vec<Dependency>, // meta: Meta, // versions: Vec<Version>, // } // // https://github.com/rust-lang/crates.io/issues/856 // #[derive(Deserialize)] // struct Dependency { // version_id: String, // } // #[derive(Deserialize)] // struct Meta { // total: u32, // } // #[derive(Deserialize)] // struct Version { // id: String, // #[serde(alias = "crate")] // crate_id: String, // } // // Main struct // 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> { // let client = reqwest::blocking::Client::builder() // .user_agent("Rust-test") // .build()?; // Ok(ReverseDependencies { // crate_id: crate_id.to_owned(), // dependencies: vec![].into_iter(), // client, // page: 0, // per_page: 100, // total: 0, // }) // } // fn try_next(&mut self) -> Result<Option<Dependency>> { // if let Some(dep) = self.dependencies.next() { // return Ok(Some(dep)); // } // if self.page > 0 && self.page * self.per_page >= self.total // { return Ok(None); // } // 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 // ); // println!("Calling {}", url); // let resp = self.client.get(url).send()?; // //println!("{:#?}", resp); // //let text = resp.text()?; // //println!("{:#?}", text); // let json = resp.json::<ApiResponse>()?; // self.dependencies = json.dependencies.into_iter(); // self.total = json.meta.total; // Ok(self.dependencies.next()) // } // } // impl Iterator for ReverseDependencies { // type Item = Result<Dependency>; // fn next(&mut self) -> Option<Self::Item> { // match self.try_next() { // Ok(Some(dep)) => Some(Ok(dep)), // Ok(None) => None, // Err(err) => Some(Err(err)), // } // } // } // fn main() -> Result<()> { // for dep in ReverseDependencies::of("bit-vec")? { // println!("reverse dependency: {}", dep?.crate_id); // } // Ok(()) // } fn main() -> anyhow::Result<()> { Ok(()) }
Handle a rate-limited API
This example uses the GitHub API - rate limiting
⮳, as an example of how to handle remote server errors. This example uses the hyper::header!
⮳ macro to parse the response header and checks for reqwest::StatusCode::FORBIDDEN
⮳ If the response exceeds the rate limit, the example waits and retries.
// use std::thread; // use std::time::Duration; // use std::time::UNIX_EPOCH; // use anyhow::anyhow; // use anyhow::Result; // use reqwest::StatusCode; // fn main() -> Result<()> { // let url = "https://api.github.com/users/john-cd"; // let client = reqwest::blocking::Client::new(); // loop { // let response = client.get(url).send()?; // let hdrs = response.headers(); // let rate_limit: usize = // hdrs.get("x-ratelimit-limit").ok_or_else( || { // anyhow!("response doesn't include the expected x-ratelimit-limit // header") } )?.to_str()?.parse()?; // let rate_remaining: usize = // hdrs.get("x-ratelimit-remaining") .ok_or_else(|| { // anyhow!("response doesn't include the expected // x-ratelimit-remaining header") })?.to_str()?.parse()?; // let rate_reset_at: u64 = hdrs.get("x-ratelimit-reset") // .ok_or_else(|| { anyhow!("response doesn't include the // expected x-ratelimit-reset header") })?.to_str()?.parse()?; // let rate_reset_within = // Duration::from_secs(rate_reset_at) - // UNIX_EPOCH.elapsed()?; // if response.status() == StatusCode::FORBIDDEN && // rate_remaining == 0 { println!("Sleeping for {} // seconds.", rate_reset_within.as_secs()); // thread::sleep(rate_reset_within); continue; // } else { // println!( // "Rate limit is currently {}/{}, the reset of this // limit will be within {} seconds.", rate_remaining, // rate_limit, // rate_reset_within.as_secs(), // ); // break; // } // } // Ok(()) // } fn main() -> anyhow::Result<()> { Ok(()) }
Download and upload
Recipe | Crates | Categories |
---|---|---|
Download a file to a temporary directory | ||
Make a partial download with HTTP range headers | ||
POST a file to paste.rs |
Download a file to a temporary directory
Creates a temporary directory with tempfile::Builder
⮳ and downloads a file over HTTP using reqwest::get
⮳ asynchronously.
Creates a target std::fs::File
⮳ with name obtained from reqwest::Response::url
⮳ within
tempfile::Builder::tempdir
⮳ and copies downloaded data into it with std::io::copy
⮳. The temporary directory is automatically removed on program exit.
use std::fs::File; use std::io::copy; use anyhow::Result; use tempfile::Builder; #[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?; let mut dest = { let fname = response .url() .path_segments() .and_then(|segments| segments.last()) .and_then(|name| if name.is_empty() { None } else { Some(name) }) .unwrap_or("tmp.bin"); println!("file to download: '{}'", fname); let fname = tmp_dir.path().join(fname); println!("will be located under: '{:?}'", fname); File::create(fname)? }; let content = response.text().await?; copy(&mut content.as_bytes(), &mut dest)?; Ok(()) }
POST a file to paste.rs
reqwest::Client
⮳ establishes a connection to https://paste.rs following the reqwest::RequestBuilder
⮳ pattern. Calling reqwest::Client::post
⮳ with a URL establishes the destination, reqwest::RequestBuilder::body
⮳ sets the content to send by reading the file, and reqwest::RequestBuilder::send
⮳ blocks until the file uploads and the response returns. std::io::Read::read_to_string
⮳ returns the response and displays in the console.
use std::fs::File; use std::io::Read; use anyhow::Result; #[tokio::main] async fn main() -> Result<()> { let mut file = File::open("temp/message")?; let mut contents = String::new(); file.read_to_string(&mut contents)?; let paste_api = "https://paste.rs"; let client = reqwest::Client::new(); let res = client.post(paste_api).body(contents).send().await?; let response_text = res.text().await?; println!("Your paste is located at: {}", response_text); Ok(()) }
Make a partial download with HTTP range headers
Uses reqwest::blocking::Client::head
⮳ to get the Content-Length
⮳ of the response.
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.
The Range header is defined in RFC7233
⮳.
use std::fs::File; use std::str::FromStr; use anyhow::Result; use anyhow::anyhow; use anyhow::bail; use reqwest::StatusCode; use reqwest::header::CONTENT_LENGTH; use reqwest::header::HeaderValue; use reqwest::header::RANGE; 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(anyhow!( "invalid buffer_size, give a value greater than zero." ))?; } Ok(PartialRangeIter { start, end, buffer_size, }) } } impl Iterator for PartialRangeIter { type Item = HeaderValue; fn next(&mut self) -> Option<Self::Item> { if self.start > self.end { None } else { let prev_start = self.start; self.start += std::cmp::min( self.buffer_size as u64, self.end - self.start + 1, ); Some( HeaderValue::from_str(&format!( "bytes={}-{}", prev_start, self.start - 1 )) .expect("string provided by format!"), ) } } } fn main() -> Result<()> { let url = "https://httpbin.org/range/102400?duration=2"; const CHUNK_SIZE: u32 = 10240; let client = reqwest::blocking::Client::new(); let response = client.head(url).send()?; let length = response .headers() .get(CONTENT_LENGTH) .ok_or(anyhow!("response doesn't include the content length"))?; let length = u64::from_str(length.to_str()?) .map_err(|_| anyhow!("invalid Content-Length header"))?; if !std::fs::exists("temp")? { std::fs::create_dir("temp")?; } let mut output_file = File::create("temp/download.bin")?; println!("starting download..."); for range in PartialRangeIter::new(0, length - 1, CHUNK_SIZE)? { println!("range {:?}", range); let mut response = client.get(url).header(RANGE, range).send()?; let status = response.status(); if !(status == StatusCode::OK || status == StatusCode::PARTIAL_CONTENT) { bail!("Unexpected server response: {}", status) } std::io::copy(&mut response, &mut output_file)?; } let content = response.text()?; std::io::copy(&mut content.as_bytes(), &mut output_file)?; println!("Finished with success!"); Ok(()) }
Web serving
Serve data over HTTP.
Actix Web
Recipe | Crates | Categories |
---|---|---|
Create a web server with Actix Web |
Axum
Recipe | Crates | Categories |
---|---|---|
Create a web server with axum |
Batteries-included frameworks
Recipe | Crates | Categories |
---|---|---|
loco | ||
Rust on Nails | Rust on Nails⮳ |
Cross-origin resource sharing
Recipe | Crates | Categories |
---|---|---|
Implement CORS |
GraphQL
Recipe | Crates | Categories |
---|---|---|
Create a GraphQL endpoint |
gRPC
Recipe | Crates | Categories |
---|---|---|
Implement gRPC |
Working with hyper
Recipe | Crates | Categories |
---|---|---|
Implement an HTTP API with hyper |
Middleware
Recipe | Crates | Categories |
---|---|---|
tower | ||
tower-http | ||
Investigate alternatives to tower |
Other frameworks
Recipe | Crates | Categories |
---|---|---|
Implement a HTTP server using rocket | ||
leptos |
Static website generators
Recipe | Crates | Categories |
---|---|---|
Create a simple website using a static website generator |
Axum
Recipe | Crates | Categories |
---|---|---|
Create a web server with axum |
Create a web server with axum
axum
is a web framework that focuses on ergonomics and modularity. It is an official part of the tokio
project. axum
is recommended for most new projects.
fn main() { todo!(); }
See also
- Axum examples⮳
- "Real world" examples with
axum
andsqlx
⮳ - Explore
realworld.how
⮳ for additional examples. crates.io
example source code (using Axum)⮳
Actix Web
Recipe | Crates | Categories |
---|---|---|
Create a web server with Actix Web |
Create a web server with Actix Web
Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust. All Rust frameworks are fast, but choose actix
if you need the absolutely maximum performance.
fn main() { todo!(); }
See also
- Actix examples⮳
- Practical Rust Web Development - API Rest⮳
- "Real world" example with Actix Web⮳
- "Real world" example with Actix Web and Diesel⮳
Other Web Frameworks
Recipe | Crates | Categories |
---|---|---|
Implement a HTTP server using rocket | ||
leptos |
Rust web framework comparison⮳
Implement a HTTP server using rocket
Web framework with a focus on usability, security, extensibility, and speed.
Rust + Rocket RealWorld framework implementation⮳
fn main() { todo!(); }
leptos
leptos
⮳ is a full-stack, isomorphic Rust web framework leveraging fine-grained reactivity to build declarative user interfaces.
fn main() { todo!(); }
Static Website Generators
Recipe | Crates | Categories |
---|---|---|
Create a simple website using a static website generator |
Create a simple website using a static website generator
Zola⮳ is a fast static site generator in a single binary with everything built-in.
fn main() { todo!(); }
Themes for zola
AdiDoks is a modern documentation theme.
Middleware
Recipe | Crates | Categories |
---|---|---|
tower | ||
tower-http | ||
Investigate alternatives to tower |
tower
tower
⮳ is a library of modular and reusable components for building robust networking clients and servers.
Tower provides a simple core abstraction, the tower::Service
⮳ trait, which represents an asynchronous function taking a request and returning either a response or an error. It can be used to model both clients and servers.
An additional abstraction, the tower::Layer
⮳ trait, is used to compose middleware with Services. A tower::Layer
⮳ is a function taking a Service of one type and returning a Service of a different type. The tower::ServiceBuilder
⮳ type is used to add middleware to a service by composing it with multiple Layers. The tower::Layer
⮳ trait can be used to write reusable components that can be applied to very different kinds of services; for example, it can be applied to services operating on different protocols, and to both the client and server side of a network transaction.
A number of third-party libraries support tower
⮳ and the tower::Service
⮳ trait: hyper
⮳, tonic
⮳ (gRPC).
tower-http
Tower HTTP
⮳ contains HTTP specific Tower utilities.
use std::iter::once;
use std::sync::Arc;
use bytes::Bytes;
use http::Request;
use http::Response;
use http::header::AUTHORIZATION;
use http::header::CONTENT_TYPE;
use http::header::HeaderName;
use http_body_util::Full;
use tower::BoxError;
use tower::ServiceBuilder;
use tower_http::add_extension::AddExtensionLayer;
use tower_http::compression::CompressionLayer;
use tower_http::propagate_header::PropagateHeaderLayer;
use tower_http::sensitive_headers::SetSensitiveRequestHeadersLayer;
use tower_http::set_header::SetResponseHeaderLayer;
use tower_http::trace::TraceLayer;
// use tower_http::validate_request::ValidateRequestHeaderLayer;
// Our request handler. This is where we would implement the
// application logic for responding to HTTP requests...
async fn handler(
_request: Request<Full<Bytes>>,
) -> Result<Response<Full<Bytes>>, BoxError> {
let empty_body = Full::new(Bytes::new());
let builder = Response::builder()
.header("X-Custom-Foo", "bar")
.status(http::status::StatusCode::OK);
Ok(builder.body(empty_body).unwrap())
}
struct DatabaseConnectionPool;
impl DatabaseConnectionPool {
fn new() -> Self {
DatabaseConnectionPool
}
}
// Shared state across all request handlers -
// in this case, a pool of database connections.
struct State {
pool: DatabaseConnectionPool,
}
#[tokio::main]
async fn main() {
// Construct the shared state.
let state = State {
pool: DatabaseConnectionPool::new(),
};
let content_length_from_response = 0;
// Use tower's `ServiceBuilder` API to build a stack of tower
// middleware wrapping our request handler.
let _service = ServiceBuilder::new()
// Mark the `Authorization` request header as sensitive
// so it doesn't show in logs
.layer(SetSensitiveRequestHeadersLayer::new(once(AUTHORIZATION)))
// High level logging of requests and responses
.layer(TraceLayer::new_for_http())
// Share an `Arc<State>` with all requests
.layer(AddExtensionLayer::new(Arc::new(state)))
// Compress responses
.layer(CompressionLayer::new())
// Propagate `X-Request-Id`s from requests to responses
.layer(PropagateHeaderLayer::new(HeaderName::from_static(
"x-request-id",
)))
// If the response has a known size set the `Content-Length` header
.layer(SetResponseHeaderLayer::overriding(
CONTENT_TYPE,
content_length_from_response,
))
//// Authorize requests using a token
//.layer(ValidateRequestHeaderLayer::bearer("passwordlol"))
//// Accept only application/json, application/* and */*
//// in a request's ACCEPT header
//.layer(ValidateRequestHeaderLayer::accept("application/json"))
// Wrap the `Service` in our middleware stack
.service_fn(handler);
}
Investigate alternatives to tower
A modular toolkit for building async web apps
Cross-origin resource sharing
Recipe | Crates | Categories |
---|---|---|
Implement CORS |
Implement CORS
use std::convert::Infallible;
use anyhow::Result;
use bytes::Bytes;
use http::Method;
use http::Request;
use http::Response;
use http::header;
use http_body_util::Full;
use tower::Service;
use tower::ServiceBuilder;
use tower::ServiceExt;
use tower_http::cors::Any;
use tower_http::cors::CorsLayer;
async fn handle(
_request: Request<Full<Bytes>>,
) -> Result<Response<Full<Bytes>>, Infallible> {
Ok(Response::new(Full::default()))
}
#[tokio::main]
async fn main() -> Result<()> {
let cors = CorsLayer::new()
// Allow `GET` and `POST` when accessing the resource
.allow_methods([Method::GET, Method::POST])
// Allow requests from any origin
.allow_origin(Any);
let mut service = ServiceBuilder::new().layer(cors).service_fn(handle);
let request = Request::builder()
.header(header::ORIGIN, "https://example.com")
.body(Full::default())
.unwrap();
let response = service.ready().await?.call(request).await?;
println!("{:?}", response);
assert_eq!(
response
.headers()
.get(header::ACCESS_CONTROL_ALLOW_ORIGIN)
.unwrap(),
"*",
);
Ok(())
}
See also
Batteries-included frameworks
Recipe | Crates | Categories |
---|---|---|
loco | ||
Rust on Nails | Rust on Nails⮳ |
loco
fn main() { todo!(); }
Rust on Nails
See also
Building a SaaS with Rust and Next.js⮳
GraphQL
Recipe | Crates | Categories |
---|---|---|
Create a GraphQL endpoint |
Create a GraphQL endpoint
async-graphql
is a high-performance graphql server library that's fully specification compliant. It integrates with actix-web
, axum
, poem
, rocket
, tide
, and warp
.
fn main() { todo!(); }
gRPC
Recipe | Crates | Categories |
---|---|---|
Implement gRPC |
Implement gRPC
tonic
implements gRPC over HTTP/2 with full support for asynchronous code. It works with tokio
.
gRPC
is an open-source high performance Remote Procedure Call (RPC) framework that can run in any environment. It can efficiently connect services in and across data centers with pluggable support for load balancing, tracing, health checking and authentication. It is also applicable in the "last mile" of distributed computing to connect devices, mobile applications and browsers to backend services.
fn main() { todo!(); }
Hyper
Recipe | Crates | Categories |
---|---|---|
Implement an HTTP API with hyper |
Implement an HTTP API with hyper
hyper
is a low-level HTTP implementation (both client and server). It implements HTTP/1 and HTTP/2. It works best with the tokio
async runtime, but can support other runtimes.
fn main() { todo!(); }
Essential Rust links
Learning Rust |
Rust Cheatsheets |
Comparison to other languages |
Books |
Example code |
Rust Blogs & Podcasts |
Newsletters |
Meetups |
Companies |
Lists of Rust links |
Websites
Key websites
rust-lang.org ⮳ | Official website for the Rust programming language |
The Rust Programming Language⮳ | "The Rust Book" |
crates.io ⮳ | The official crate (library) registry |
crates.io 's categories⮳ | List of categories on crates.io - search with category:<category-slug> |
lib.rs ⮳ | Curated, unofficial alternative to crates.io |
docs.rs ⮳ | The central documentation website for Rust crates |
blessed ⮳ | Unofficial guide to (the best of) the Rust ecosystem |
github.com ⮳ | The home of most Rust code repositories |
Lists of Rust links
Example code
Example codebases include:
- A full template for a REST API written in Rust, engineered for maximum testability: rust-rest⮳
RealWorld
- A fullstack RealWorld implementation using Rust, Axum, sqlx, and Yew: realworld-rust-axum-sqlx⮳
Source code for Rust books
- code⮳ for Zero To Production In Rust (book)⮳.
- Source Code for 'Practical Rust Web Projects'⮳ by Shing Lyu.
Rust Cheatsheets
Cheatsheets
Comparison to other languages
Blogs, Podcasts, Meetups
- The official Rust blog
- A learning journal (lpalmieri.com)⮳
- LukeMathWalker/zero-to-production: Code for "Zero To Production In Rust", a book on API development using Rust⮳
codevoweb.com
⮳, demonstrating how to build common aplications using multiple languages, including Rust
Rust podcasts
Newsletters
shuttle.rs/launchpad
has tutorial-style lessons and real-world examples.
Meetups
Rust Meetup Linz
Books
- The Little Book of Rust Books⮳
- "The Book": The Rust Programming Language⮳
- Rust by Example⮳
- Rust by Practice⮳
- Rust Cookbook (fork)⮳
- Rust Cookbook (old)⮳
- Rust Design Patterns⮳
- Easy Rust⮳
- The Rustonomicon⮳
- Effective Rust⮳
- Programming Rust⮳
- Programming Rust, 2nd edition⮳
- The Rust Programming Language, 2nd Edition⮳
- Writing Interpreters in Rust: a Guide⮳
- Asynchronous Programming in Rust⮳
- The
rustdoc
book⮳
add Rust Atomics
Companies that use or contribute to Rust
The following is an (incomplete) list of companies and organizations that use Rust. It combines information from the following sources, among others:
rust-companies
(github)- the member list of the Rust Foundation.
Please also consult the theirstack.com
's list of companies using Rust.
1Password⮳(github)⮳ | All desktop and mobile apps share a single Rust codebase. |
360Dialog⮳ | Uses Rust for most of their service consumers. |
49Nord⮳ | Develops safe and secure industrial IoT hardware and applications using Rust. |
9fin⮳ | |
ANIXE⮳ | Builds their travel services trading platform in Rust. |
ARM⮳ | |
Accelerant⮳ | |
Actyx⮳(github)⮳ | Actyx is a peer-to-peer event stream database (a.k.a. reliable durable pub-sub) written completely in Rust. |
Adacore⮳(blog)⮳ | |
Afterpay⮳ | |
Airborne Engineering⮳ | |
Alchemy⮳ | |
Amazon⮳ | Amazon Web Services has adopted Rust for certain infrastructure, networking, and machine learning services, including their serverless computing - see, for example, Amazon Firecracker⮳. |
AppFlowy⮳ | |
AppSignal⮳ | Provides monitoring products for web developers. |
Apple⮳ | |
Aptos Labs⮳ | |
Astral⮳(github)⮳ | |
Atlassian⮳ | Uses Rust to analyze petabytes of source code. |
Attain⮳ | |
Automata⮳ | |
Autumn Engineering⮳ | Offers machine learning frameworks written in Rust. |
Bencher⮳(github)⮳ | Continuous benchmarking tools designed to catch performance regressions in CI. |
Bitfury⮳ | Exonum is an extensible framework for blockchain projects written in Rust. |
Block⮳ | |
Brainport Digital Factory⮳ | |
Braintree Payments⮳ | Uses Rust for command-line utilities and batch processing as part of their third-party payment merchant services. |
Braun Embedded⮳(github)⮳ | Provides firmware development services for ARM Cortex-M microcontrollers. |
Brave⮳(github)⮳ | Uses Rust for its browser's ad-blocking engine. |
Buoyant⮳ | |
Calyptech⮳ | Used for high performance embedded system components as an alternative to C. |
Canonical⮳ | Everything from server monitoring to middleware! |
CarGurus⮳ | |
Cash App⮳ | |
Ceph⮳ | Provides an open-source storage system partially written in Rust. Rust bindings for librbd, an interface into the Ceph storage platform. |
Chef⮳(github)⮳ | Offers a platform to configure, deploy, and manage applications, written in Rust. |
Chroma⮳(github)⮳ | |
Clever Cloud⮳ | Allow developers to host web applications. A part of their infrastructure is developed in Rust. |
Cloudflare Workers⮳ | Deploy serverless code across the globe with Rust support. |
Cloudflare⮳(github)⮳ | Uses Rust for its core edge logic. See, for example, Pingora (blog)⮳. |
CodeDay⮳ | |
CoreOS⮳ | CoreOS is s a open-sourced container host, using Rust as one of its main programming languages. |
Corrode⮳(github)⮳ | Germany-based Rust consulting and development firm. |
Coursera⮳ | Uses Rust for its learning platform. |
Craft⮳ | Their core machine learning engine is written in Rust. |
Cryptape⮳ | Their business intelligence platform built for blockchain is written in Rust. |
CurrySoftware GmbH⮳ | |
DataRobot⮳(github)⮳ | Builds Machine Learning models that are implemented using Rust. |
Deepgram⮳(github)⮳ | Speech AI Platform for transcription and understanding. |
Delimiter⮳ | Rust powers their bare metal provisioning system. |
Deno⮳ | Uses Rust for its JavaScript and TypeScript runtime. |
Devolutions⮳ | |
Dioxus Labs⮳(github)⮳ | |
Discord⮳ | Uses Rust for both the client and server sides of its codebase, including their real-time communication services. |
Disney⮳ | |
Distil Networks | Uses Rust in their low-latency bot detection and mitigation platform. |
Dropbox⮳ | Uses Rust for its file-syncing engine. See, for example, Capture⮳. |
DungeonFog⮳(github)⮳ | Uses Rust in their game engine and map-making tool for RPGs. New "Project Deios" map maker tool is written completely in Rust. |
EVO⮳ | Develops marketplaces for online shopping, fintech, and logistics. Development tools, containerization, monitoring and orchestration systems written in Rust. |
Embark Studios⮳ | |
Embecosm⮳ | |
Espressif⮳(github)⮳ | Improving performance of embedded and IoT devices with using Rust in esp products. |
Exonum⮳ | Supports smart contracts written in Rust. |
Faraday⮳(github)⮳ | Embeds AI in workflows. |
Fedi⮳ | Fedi builds on Fedimint⮳, a module based system for building federated applications on top of Bitcoin. |
Fermyon⮳ | |
Ferrous Systems⮳(github)⮳ | Ferrous is a Rust consulting firm specializing in embedded systems development. |
Figma⮳ | Uses Rust for parts of its design and collaboration tools. Their real-time multiplayer syncing server (used to edit all Figma documents) is written in Rust. |
Firo Solutions⮳(github)⮳ | Firo Solutions is a notification service that notifies the end user about security vulnerabilities. Parse large amounts of data using Rust. |
Flowdesk⮳ | |
Fly⮳(github)⮳ | Globally distributed reverse-proxy and app hosting. |
Freiheit⮳ | |
GRAIL⮳ | |
Gama Space⮳ | |
Gitoxide Labs (github)⮳ | |
Google⮳ | Uses Rust for ChromeOS, Google Fuchsia, and other projects. In Android 13, about 21% of all new native code (C/C++/Rust) is in Rust. There are approximately 1.5 million total lines of Rust code in Android across new functionality and components such as Keystore2, the new Ultra-wideband (UWB) stack, DNS-over-HTTP3, Android’s Virtualization framework (AVF), and various other components and their open source dependencies. |
Grafbase⮳ | |
Greenbone⮳ | |
Gremlin⮳ | Safely and efficiently causing controlled chaos. |
HighTec EDV Systeme⮳ | |
Hive⮳ | Offers pre-trained AI models for automating content moderation. Hive 2.0 is written in Rust. |
Hove⮳(github)⮳ | Navitia API provides ways to query public transport data, including a multi-criteria journey engine. |
Huawei⮳ | Develops telecommunication equipment and consumer electronics, in part via Rust. |
HuggingFace⮳(github)⮳ | Many components within the Hugging Face ecosystem for AI, including safetensors, tokenizer, and candle, are implemented using Rust. |
Hyperswitch (Juspay.io)⮳(github)⮳ | Their open-source payment switch that allows businesses to interact with multiple payment gateways is built with Rust. |
Immunant⮳(github)⮳ | Immunant specializes in translation from C to Rust and exposing legacy C/C++ through safe Rust interfaces. |
Infinyon⮳ | |
Integer32⮳(github)⮳ | Integer 32 is a consultancy that delivers high-quality Rust code. |
JFrog⮳ | |
Jetbrains⮳ | |
KDAB⮳ | |
Keyrock⮳ | |
Knoldus⮳ | |
Kong⮳ | |
Kraken⮳ | Cryptocurrency exchange, based in the USA. |
Lechev.space⮳(github)⮳ | Building various aerospace-related projects including a Ground station service using Rust. |
Leptos⮳(github)⮳ | |
Linebender⮳ | See, for example, Vello⮳ |
Linkerd⮳ | |
Lynx⮳ | |
Machina Labs, Inc⮳ | |
Maidsafe⮳ | Builds a decentralized data and communication network. |
Mainmatter⮳ | |
Maplibre⮳(github)⮳ | |
Materialize⮳(github)⮳ | Uses Rust in their streaming data warehouse. An engine for incrementally maintaining database views. Materialize core is a single binary written in Rust. |
Meilisearch⮳(github)⮳ | |
Memfault⮳ | |
Meta⮳ | Uses Rust for the source control backend, Libra (now Diem), and other server-side projects. Facebook's primary source control system is partially written in Rust. |
Microsoft⮳ | Uses Rust for various projects, including the Windows kernel, Azure, and Visual Studio. windows-rs allows you to call Windows API using Rust. There are several Azure services also using Rust, including Azure IoT Edge⮳ and Kusto, the core query and storage engine for Azure Data Explorer⮳. |
MoveParallel⮳ | |
Mozilla⮳(github)⮳ | The creators of Rust, Mozilla has used the language in various projects, including parts of the Firefox web browser. See Servo⮳. |
Multi media, LLC⮳ | |
NextRoll⮳ | |
Novo Nordisk⮳ | |
Npm⮳(github)⮳ | The package manager for JavaScript has components written in Rust. Also use Rust for their authorization service. |
Nvim⮳ | Uses Rust for its powerful text editor. |
OVHcloud⮳ | Use Rust to build a high performance, highly available log management system. |
OneSignal⮳(blog)⮳ | High volume, cross platform push notification delivery. |
OpenUK⮳ | |
Open Source Security⮳ | |
OpenSource Science (OS-Sci)⮳ | |
OxidOS Automotive⮳ | |
PUCPR⮳ | |
Parity Technology⮳(github)⮳ | Creates open-sourced networks, consensus protocols, and cryptography, entirely in Rust. |
Personio⮳ | |
Pollen Robotics⮳ | |
PolySync⮳ | Builds safety-critical runtime environments & infrastructure for autonomous vehicles. |
Postmates⮳ | |
Qdrant⮳(github)⮳ | High-performance, massive-scale Vector Database and Vector Search Engine for the next generation of AI |
Qumulo⮳ | Offers a hybrid data storage for large file and object data. |
Qwiet⮳ | |
Readyset⮳ | |
Red Badger⮳ | |
Red Hat⮳ | Engages with Rust in several projects, particularly in areas related to system programming and container technology. |
Red Iron⮳(github)⮳ | Red Iron is the Rust division of OCamlPro, a French consultancy specializing in programming languages, formal methods and high reliability software. |
RedJack⮳ | |
Red Sift⮳(github)⮳ | Container monitoring with eBPF. |
Renault⮳ | French automotive manufacturer. |
Routific | High performance route optimization software. |
RustNL⮳ | |
Rustdesk⮳(github)⮳ | |
Salesforce ⮳ | Uses Rust & WASM for MuleSoft's Flex Gateway, a new gateway running on Envoy. |
SandboxVR⮳ | |
Sandstorm⮳ | The backend of their Collections app is written in Rust. |
Scythe Robotics⮳ | |
Sensirion⮳ | |
Sentry⮳ | Uses Rust for its performance-critical components. JavaScript, Java and iOS event processing and the command-line client for the Sentry API. |
Shopify⮳ | |
Slint⮳(github)⮳ | |
Slowtec⮳ | Their automation systems are entirely written in Rust. |
SmartThings (Samsung)⮳ | Memory-safe embedded applications on our SmartThings Hub and supporting services in the cloud. |
Snips (Sonos)⮳ | AI assistants that are private by design. |
Solana Labs⮳ | Use Rust as their main programming language for smart contracts. |
SpectralOps⮳ | |
Sprinklenet⮳ | |
Spruceid⮳ | |
Square⮳ | |
Stackable⮳(github)⮳ | Open-source software development company that handles data. Creating a modular open source data platform. Rust powers all our operators to help make popular data applications straightforward to run on Kubernetes. |
Starlab⮳ | Provides embedded security. |
Starry⮳ | Re-imagining broadband by engineering a new wireless access network. |
Sui⮳ | |
Svix⮳(github)⮳ | Enterprise-grade webhooks service. |
System76⮳(github)⮳ | Manufacturer of Linux computers and keyboards. As a Linux-based computer-manufacture, much of their infrastructure and desktop Linux projects are written in Rust. |
Tag1 Consulting⮳ | |
Tailcall⮳(github)⮳ | A cloud native solution to streamline API management across edge, middle, and service layers. |
Tarro⮳ | |
Techfund⮳ | |
Terminal Technologies⮳ | |
Tesla⮳ | |
Threema⮳ | |
Tikv⮳(github)⮳ | |
TockOS⮳(github)⮳ | |
Trace Machina⮳ | |
Tracel.ai⮳(github)⮳ | |
TreeScale⮳ | TreeScale implements distributed PubSub system using Rust and MIO |
Tremor⮳(github)⮳ | |
Tsy Capital⮳ | |
Tweede Golf⮳(github)⮳ | Create safe internet infrastructure with Rust and secure connected devices with Embedded Rust. |
Vail Systems⮳ | |
Veecle⮳ | |
VersionEye⮳(github)⮳ | Use Rust to implement a command line tool which can identify software dependencies by their SHA values. |
Volvo⮳ | |
Warp⮳ | |
Watchful⮳ | |
Wildfish⮳ | Fast processing and importing of cryptocurrency market data. |
WyeWorks⮳ | |
Wyliodrin⮳ | |
Xfusion⮳ | |
Yelp⮳ | |
Yomura Fiber⮳ | Rust powers their GPON provisioning and statistic gathering. |
ZS⮳ | |
Zama⮳(github)⮳ | Open source cryptographic tools that make protecting privacy easy. |
Zeplin⮳ |
Jobs
Learn Rust
- Rust language main site: rust-lang.org
Rust by Example
⮳Rust by practice
⮳Comprehensive Rust
⮳, a free Rust course developed by the Android team at Google. The course covers the full spectrum of Rust, from basic syntax to advanced topics like generics and error handling.Rust for professionals
⮳A half-hour to learn Rust
⮳Awesome Rust
⮳: a curated list of Rust code and resources.- Top 15 Rust Projects To elevate your skills: Rust practice projects⮳
- Rust mastery exercises⮳
rust.code-maven.com
- Rust Learning⮳: a bunch of links to blog posts, articles, videos, etc for learning Rust
- Learning Rust With Entirely Too Many Linked Lists⮳
- Rust quizz⮳
- Open source training courses about distributed database and distributed systems: Talent plan⮳
- Learning Rust By Practice, narrowing the gap between beginner and skilled-dev through challenging examples, exercises and projects: rust-by-practice⮳
- A bunch of links to blog posts, articles, videos, etc for learning Rust rust-learning
Rust Language Bible
- How to learn modern Rust
Ferrous systems
Call for contributions
This book is in its early days. Contributions, from small edits to whole chapters, are most welcome.
It is also intended to be easy for (new) Rust programmers to contribute to and get involved with the Rust community.
Feel free to submit an issue or a pull request to the repo⮳. Draft pages are kept in this folder⮳. An informal (and very long) list of subjects we would like to cover is kept in the topics of interest⮳ page.
Embedded examples should be ideally runnable on the Rust playground⮳ or at least directly copy-pasteable into Rust code.
This book's long-term goal is the coverage of the 'most commonly used' Rust crates, as defined by blessed.rs
⮳, the most downloaded libraries in crates.io
⮳, and 'high quality crates' per lib.rs
⮳ statistics⮳. Review key crates⮳ for topic ideas.
Unless you explicitly state otherwise, any contribution you intentionally submit for inclusion in this book shall be licensed under the same terms than the rest of the book, without any additional restrictions or conditions.
Please read CONTRIBUTING.md for more details.
See also
Topics of interest
The following are topics that deserve (additional) coverage and examples:
- Rust GUI
- Rust patterns
- Rust Macros
- Advanced data structures
- Testing
- GPU processing, CUDA
- Machine learning, Tensorflow
- Template Engines
- Caching
- Compilers
- WASM
- Algorithms
- Rust patterns
- Authentication / authorization: OAuth2, LDAP/AD, DNS lookups...
- Continuous Deployment & Integration (CD / CI) for Rust projects
- Use of Rust in AWS and other Cloud services
- Serverless Rust
- More database examples, including object databases, graph databases, e.g. BonsaiDB, neo4j
- Embedded
- Visualization
- Graphics
- Games, Game Engines
- Search engines
- Compression: Zip files and other archives
- Buffer pools, garbage collection, or other reference-counted examples
- IPv6 address processing
- Cloud: load balancers, status reporting (Vigil), routing, orchestration, containers
- Reverse proxies
- Web programming
- Virtualization
- Version control: libgit2: clone, change branches, create commits, push, pull
- Crypto, SSL, SSH, other public key encryption, X.509, RusTLS
- Network programming: Basic and advanced TCP/IP networking
- Interfacing with FLTK (Fast Light Tool Kit)
- Raft Consensus library
- Network file systems
- Statistics, math
- Sound
- API bindings
- FFI
- Hardware support
- Build Utils
- Rendering
- High-performance computing: OpenMP, etc.
- Social media APIs
- Personal file sharing: OwnCloud, etc.
- Emulators
- Accessibility
- Internationalization
- Localization
- Multimedia
- Computer Vision
- Robotics
- Simulation
- Science (Geo, Neuro)
- Finance
- Aerospace-related crates: drones, UAVs, space protocols, simulation...
- Cryptocurrencies
Please also consult the TODO.md
file and the drafts
folder.
Repo structure
- The repo contains a book, which markdown sources are in the
src
folder. - After the book is built using
mdbook
⮳, the resulting HTML and Javascript are found inbook/html
. - The intermediate (processed) Markdown is in
book/markdown
. Themdbook
⮳ configuration is inbook.toml
; the templates and assets are intheme
andstatic
respectively. - The Rust code is organized as a
cargo
⮳ workspace:- Examples that are embedded in the book are found in crates below
crates/ex
, named after sections of the book or grouping multiple (crates.io) categories of examples. Each example is in a single, short.rs
file. TheCargo.toml
within these crates list the dependencies used by the embedded examples. Usecargo add <crate> -F <feature>
while in the appropriate crate folder to add more as required. - Additional examples that are too long or complex to be inserted in the book itself can be added under
crates/xmpl
. crates/tools
contains utilities to build sections of the book, for example some indices.
- Examples that are embedded in the book are found in crates below
- The Dev Container and Docker (Compose) configuration files are found in
.devcontainer
.
All examples are fully and continuously tested
In order to make sure that all examples work, they are backed by tests, similar to the following:
[test]
fn test() {
main();
}
For the sake of readability, that boilerplate is hidden by default. In order to read the full contents, click on the "expand" () button located in the top right corner of the code snippets.
Development Environment Setup
Using VS Code
Clone the repo⮳ and open the folder in VS Code⮳. Edit .devcontainer/.env
if needed. VS Code should prompt you to open the code in a docker
⮳ container, which installs mdbook
⮳ and rust tooling automatically. Make sure you have previously installed
- Dev Container extension⮳
- Docker Desktop]⮳ (or at least the Docker engine).
Note that opening the code folder in VS Code may take a little while the first time around.
Other
If you are not using VS Code, install the Dev Container CLI⮳ or simply install the required tools on your local machine:
sudo apt-get update # or equivalent for other distros
# sudo apt-get install fzf # optional
# sudo apt-get mold clang # if using
rustup update
rustup component add clippy
cargo install cargo-nextest
cargo install mdbook
cargo install just
cargo install mdbook_linkcheck
cargo install mdbook-utils
# for cargo +nightly fmt
rustup toolchain install nightly
rustup component add rustfmt --toolchain nightly
You may need sudo apt-get install libsqlite3-dev
on WSL.
Review .devcontainer/Dockerfile
for other dependencies.
dev_environment_setup: windows install (P1)
winget install openssl
need Python
Alternative just
install
# RUN <<EOF
# set -e
# wget -qO - '<https://proget.makedeb.org/debian-feeds/prebuilt-mpr.pub>' | gpg --dearmor | sudo tee /usr/share/keyrings/prebuilt-mpr-archive-keyring.gpg 1> /dev/null
# echo "deb [arch=all,$(dpkg --print-architecture) signed-by=/usr/share/keyrings/prebuilt-mpr-archive-keyring.gpg] https://proget.makedeb.org prebuilt-mpr $(lsb_release -cs)" | sudo tee /etc/apt/sources.list.d/prebuilt-mpr.list
# sudo apt update
# apt-get -y install just
# EOF
Alternative mdbook
install
# RUN <<EOF
# set -e
# wget -c <https://github.com/rust-lang/mdBook/releases/download/v0.4.36/mdbook-v0.4.36-x86_64-unknown-linux-gnu.tar.gz> -O - | sudo tar -xvz -C /usr/local/bin
# EOF
Book Editing and Example Code Development
Type just
⮳ (a tool similar to make
⮳) in your favorite shell to lists all commonly used recipes during book editing and example code development.
Use just serve
to preview the book by serving it locally on http://localhost:3000⮳.
To add or edit the book, simply update or add a .md
file in the appropriate src
⮳ subfolder, then add a link in SUMMARY.md
⮳.
- Add Rust code examples under
crates/ex/<crate_name>/tests/<folder e.g. category>
⮳.- Make sure to format your code (
just fmtall
orcargo +nightly fmt --all
), check it compiles (just buildall
orcargo build --all-targets
), lint it (just clippyall
orcargo clippy --all-targets
), and test it (just testall
orcargo test --test <name>
for an individual example). You may alsocargo run --example <name>
. - Include your code example in the Markdown via
{{# include /path/to/file.rs}}
within pairs of triple backticks.
- Make sure to format your code (
- You may write very short examples directly in the Markdown (but they won't be be formatted / linted automatically).
rust
language code blocks in the Markdown will automatically get a play button, which will execute the code in the Rust Playground⮳ and display the output just below the code block. Adding themdbook-runnable
attribute forces the play button to be displayed whenignore
⮳ is set.- The Rust playground only supports the top 100 most downloaded libraries and libraries used by the Rust Cookbook.
noplayground
⮳ removes the play button if a code block does not work on the playground. - Example projects that are too complex to be inserted in the book itself (e.g. that include multiple modules) should be added as separate folders below
crates/xmpl
. Usecargo new
orcargo init
to create packages as usual. Insert a link to the appropriate GitHub page in the markdown.
Verify the markdown is properly rendered using just serve
or mdbook serve --open
. Pushing a commit to the main
branch on GitHub will trigger a GitHub Action workflow that checks formatting / linting, builds / tests all examples, then deploys the book to GitHub Pages.
Dev Container and Docker
The development
target of the multi-stage .devcontainer\Dockerfile
is used by .devcontainer/devcontainer.json
to install mdbook
⮳ and rust tooling.
If you don't want to use Dev Container, use the following from the project's root directory to manually build the docker
⮳ image and run it.
docker build --file .devcontainer/Dockerfile --target development --tag rust_howto_dev --build-arg RUST_IMAGE_LABEL=1.75.0-slim-bookworm --build-arg MDBOOK_VERSION=0.4.36 .
docker run --rm --detach --name rust_howto_dev1 --volume $(pwd):/code rust_howto_dev
docker exec -it rust_howto_dev1 bash
To cache the crate and the target folders from run to run, add
--mount type=volume,src=rust_howto_cargo_crate_cache,dst=/usr/local/cargo/registry/
--mount type=volume,src=rust_howto_cargo_target_cache,dst=/cargo-target-rust_howto/
To connect to the (host OS) docker engine from within the container, add
--mount type=bind,src=/var/run/docker.sock,dst=/var/run/docker-host.sock
Docker Compose
Test the docker compose setup used during developement (which Dev Container runs) with:
cd ./.devcontainer
docker compose build # uses compose.yaml and compose.override.yaml
docker compose up -d
# or simply
docker compose up --build -d
Deployment to GitHub Pages
The continuous integration worflow is found under .github
.
Test the docker compose setup used during CI using:
cd ./.devcontainer
docker compose -f compose.yaml -f compose-ci.yaml build
docker compose -f compose.yaml -f compose-ci.yaml run book # or simply docker compose -f compose.yaml -f compose-ci.yaml up
It uses the ci
target in .devcontainer/Dockerfile
.
To test the docker
⮳ image manually, use
docker build --file .devcontainer/Dockerfile --target ci --tag rust_howto_ci --build-arg RUST_IMAGE_LABEL=1.75.0-slim-bookworm --build-arg MDBOOK_VERSION=0.4.36 .
docker run -it --rm --name rust_howto_ci1 --volume $(pwd)/book:/code/book rust_howto_ci bash
Related Stackoverflow question⮳
Push image to Docker Hub
From the project root folder, use the following to build and push the development
image:
docker build --file .devcontainer/Dockerfile --target development --tag johncd/rust_howto_dev:latest --build-arg RUST_IMAGE_LABEL=1.75.0-slim-bookworm --build-arg MDBOOK_VERSION=0.4.36 .
# or docker tag rust_howto_dev johncd/rust_howto_dev:latest
docker login
# or docker login -u "user" -p "password" docker.io
docker push johncd/rust_howto_dev:latest
Use the following to build and push the CI image:
docker build --file .devcontainer/Dockerfile --target ci --tag johncd/rust_howto_ci --build-arg RUST_IMAGE_LABEL=1.75.0-slim-bookworm --build-arg MDBOOK_VERSION=0.4.36 .
docker login
docker push johncd/rust_howto_ci:latest
- rust and Docker; multistage builds
Optional pre-processors
mdbook-linkcheck
⮳ is a backend formdbook
⮳ that will check links. Install withcargo install mdbook-linkcheck
. Uncomment the related section inbook.toml
.
mdbook-hide
⮳ hides chapters under construction. Install withcargo install mdbook-hide
. Uncomment the related section inbook.toml
. To mark a chapter as hidden, add the following comment anywhere in the Markdown file. It is better to have it at the top of the file for clarity.
<!--hidden-->
mdbook-keeper
⮳. Install with
cargo install mdbook_keeper --git https://github.com/tfpk/mdbook_keeper.git
Generate the docs.rs
Documentation
Generate the docs.rs Documentation |
Using a Dev Container feature |
Other methods to preview the documentation HTML |
Use just doc
to generate the documentation for docs.rs
.
cargo doc --open
does not seem to work when running from a Dev Container in VS Code; the script that opens URLs into an external browser (see $ echo $BROWSER
) does not handle raw HTML. Use python3 -m http.server 9000
or live server to serve the files instead. See the doc
recipe in justfile
⮳.
Using a Dev Container feature
Alternatively, use the "Desktop lite" Dev Container feature⮳ to install a light GUI manager. Add the following to devcontainer.json
:
"features": {
"ghcr.io/devcontainers/features/desktop-lite:1": {}
},
"forwardPorts": [
6080
],
"portsAttributes": {
"6080": {
"label": "desktop"
}
},
and the following to the Dockerfile
⮳
RUN apt-get update && export DEBIAN_FRONTEND=noninteractive && apt-get install -y firefox-esr
Optionally apt-get install xdg-utils
to check that Firefox is the default for text/html
:
xdg-mime query default text/html
# or for more details:
XDG_UTILS_DEBUG_LEVEL=2 xdg-mime query default text/html
xdg-settings --list
xdg-settings get default-web-browser
Point your browser to http://localhost:6080 and use vscode
as the password. Open the HTML file of your choice with:
xdg-open /cargo-target-rust_howto/target/doc/deps/index.html
Other methods to preview the documentation HTML
- Add the target directory e.g.
/cargo-target-rust_howto/target
to the VS Code Explorer view (File
>Add Folder to Workspace...
), then right-click the/cargo-target-rust_howto/target/doc
folder in the VS Code Explorer view and selectDownload...
or use VS Code's built-inSimple Browser
command. - Or install the
Live Server
or MSLive Preview
VS Code extensions.
Publish to crates.io
The crates/publish
folder contains a placeholder crate, so that the book could be located when searching on crates.io
.
cargo update
if necessary- Go to
crates.io
, sign in, and create an API token inAccount Settings
>API Tokens
. - Use
cargo login
to save the token in$CARGO_HOME/credentials.toml
. cd crates/publish
cargo build --locked --release
cargo clippy
cargo run --release
cargo doc
- Review
cargo package --list
cargo package
- Review the packaging output in
/cargo-target-rust_howto/target/package
. - When ready,
cargo publish --dry-run; cargo publish
Index
*, 1
(), 1
.gitignore, 1
Accessibility, 1
Ace, 1
Actix, 1
actix-web, 1, 2, 3, 4, 5
Aead, 1, 2
Aes, 1, 2
Aes-gcm, 1
aes-gcm, 1, 2
aes-gcm-siv, 1, 2
Algorithms, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22
Allocation, 1
Ansi, 1, 2, 3, 4, 5
ANSI terminals, 1
Ansi_term, 1
ansi_term, 1, 2, 3
ansi_term::ANSIString, 1, 2
ansi_term::Color, 1
ansi_term::Style, 1, 2, 3
ansi_term::Style::new, 1
ansi_term:Style, 1, 2
ansiterm, 1
anstream, 1
anstyle, 1
anyhow, 1, 2, 3, 4
anyhow::Result, 1
API bindings, 1, 2, 3
apk, 1
App_dirs, 1, 2
application/x-www-form-urlencoded, 1
approx, 1, 2, 3
approx::assert_abs_diff_eq, 1
Arc, 1, 2
arc-swap, 1
Argument parsers, 1
Arguments, 1
arrayvec, 1
Asn1, 1
asref, 1
Assertions, 1
Async, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12
async, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12
Async channels, 1, 2
async fn, 1
async-channel, 1, 2, 3
async-graphql, 1
async-std, 1, 2, 3, 4, 5, 6
async-stream, 1, 2
async-trait, 1, 2, 3, 4
async/await, 1
Asynchronous, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36
Asynchronous programming, 1
Asynchronous runtime, 1
Atomic, 1, 2, 3, 4, 5
Atomic types, 1
Atomics, 1
Attributes, 1, 2, 3
Authentication, 1
Autotraits, 1
await, 1, 2, 3, 4
axum, 1, 2, 3, 4
bacon, 1, 2, 3, 4
Badges, 1
Base URL, 1
Base32, 1, 2
Base64, 1, 2
base64, 1, 2, 3
base64::decode, 1
base64::encode, 1
Basedir, 1, 2
Bash, 1
bat, 1, 2, 3
bevy, 1, 2, 3, 4
Big-endian, 1
Bignum, 1, 2, 3
Binary, 1, 2
Binary crate, 1
bincode, 1
Bit, 1
bitfield, 1
bitflags, 1, 2, 3
bitflags::bitflags, 1
Bitmask, 1
Bitwise operations, 1
blessed.rs, 1, 2, 3
Blocking code, 1
Blocking operation, 1
Blogs, 1
bon, 1
Book, 1, 2
Books, 1
bool, 1
Borrowing, 1, 2
bottlerocket, 1, 2
Boundary, 1
Box, 1
broot, 1, 2
Build metadata, 1
Build Utils, 1, 2
Build utils, 1, 2
build.rs, 1
Builder, 1, 2, 3
Byte, 1
Byte order, 1
bytemuck, 1
byteorder, 1, 2, 3, 4
bytes, 1, 2
Caching, 1, 2
Calendar, 1
Capture of variables, 1
Cargo, 1, 2, 3, 4, 5, 6
cargo, 1, 2, 3, 4
Cargo plugins, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19
cargo plugins, 1
cargo-audit, 1
cargo-auditable, 1, 2, 3, 4
cargo-binstall, 1
cargo-cache, 1, 2, 3, 4, 5
cargo-crates, 1, 2, 3
cargo-deny, 1
cargo-edit, 1, 2, 3
cargo-expand, 1, 2, 3, 4, 5
cargo-generate, 1, 2
cargo-hack, 1, 2, 3, 4
cargo-hakari, 1, 2, 3
cargo-husky, 1, 2, 3
cargo-license, 1
cargo-limit, 1
cargo-machete, 1
cargo-make, 1, 2, 3, 4, 5, 6
cargo-nextest, 1, 2
cargo-outdated, 1
Cargo-subcommand, 1
cargo-tarpaulin, 1
cargo-udeps, 1, 2, 3
cargo-watch, 1
cargo-wizard, 1, 2, 3
cargo-xtask, 1, 2
cargo-zigbuild, 1
Cargo.lock, 1, 2
Cargo.toml, 1
Cbor, 1
cc, 1, 2, 3, 4, 5, 6
cc::Build, 1, 2
cc::Build::compile, 1
cc::Build::cpp, 1
cc::Build::define, 1
cc::Build::flag, 1
cc::Build::include, 1
Ccache, 1
cdylib, 1
chacha20poly1305, 1
Chan, 1
Channel, 1, 2, 3
Channels, 1, 2, 3, 4, 5
Cheat sheets, 1
chrono, 1, 2, 3, 4, 5, 6, 7, 8
chrono::Date::checked_add_signed, 1
chrono::Date::checked_sub_signed, 1
chrono::Datelike, 1
chrono::DateTime, 1, 2, 3
chrono::DateTime::format, 1, 2, 3
chrono::DateTime::from_utc, 1
chrono::DateTime::parse_from_rfc2822, 1, 2
chrono::DateTime::parse_from_str, 1, 2, 3
chrono::DateTime::to_rfc2822, 1
chrono::DateTime::to_rfc3339, 1
chrono::format::strftime, 1, 2, 3
chrono::naive::NaiveDate, 1
chrono::naive::NaiveDate::from_ymd, 1
chrono::naive::NaiveDateTime, 1
chrono::naive::NaiveDateTime::from_timestamp, 1
chrono::naive::NaiveDateTime::timestamp, 1
chrono::naive::NaiveTime, 1
chrono::naive::NaiveTime::from_hms, 1
chrono::offset::FixedOffset, 1
chrono::offset::Local::now, 1, 2
chrono::offset::Utc::now, 1
chrono::Timelike, 1
ciborium, 1
Clap, 1
clap, 1, 2, 3, 4, 5, 6
clap::Arg::long, 1
clap::Arg::short, 1
Cli, 1, 2, 3, 4
Client, 1
clippy, 1
Clone-on-write, 1
Closures, 1
Cmd, 1
Code example, 1
Coercion, 1
Color, 1, 2, 3, 4, 5, 6
color-eyre, 1
Colors, 1
Command line utilities, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13
Command runner, 1
Command-line, 1
Command-line interface, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14
Comparison to other languages, 1
Compression, 1, 2, 3, 4
concat-string, 1
concat_strs, 1, 2
Concatenation, 1
Concurrency, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45
Concurrent, 1, 2
Concurrent associative array, 1
Concurrent contexts, 1
Condvar, 1
Config, 1
config, 1, 2, 3, 4
Configuration, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11
Configuration management, 1
confy, 1
Console, 1
console, 1, 2
const, 1
Constant-time, 1
Constructor, 1
Consumer, 1, 2
Content length, 1
Content type, 1
Contents, 1
Continuous integration worflow, 1
Control flow, 1
Cooperative multitasking, 1
Cores, 1
cornucopia, 1, 2, 3
cos, 1
cosmic-text, 1
Cow, 1
CPU, 1
Cpu, 1
CPU bound, 1
CPU cores, 1
CPU-heavy computations, 1
Cpus, 1
Crate, 1, 2
Crate root file, 1
Crates, 1
crates.io, 1, 2, 3, 4, 5
crates_io_api, 1, 2
Cross-interpretation, 1
crossbeam, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11
crossbeam spawn, 1
crossbeam-channel, 1, 2, 3, 4, 5, 6, 7
crossbeam-queue, 1, 2, 3, 4
crossbeam-utils, 1, 2, 3, 4
crossbeam::atomic::AtomicCell, 1
crossbeam::scope, 1, 2, 3, 4
crossbeam::thread::Scope::spawn, 1, 2
crossbeam_channel, 1
crossbeam_channel::bounded, 1
crossbeam_channel::Receiver::iter, 1
crossbeam_channel::Sender::send, 1
crossterm, 1
Crypto, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16
Cryptography, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28
csv, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11
csv::ByteRecord, 1
csv::invalid_option, 1
csv::Reader::deserialize, 1, 2
csv::ReaderBuilder::delimiter, 1
csv::StringRecord, 1
csv::Writer, 1
csv::Writer::flush, 1
csv::Writer::serialize, 1
csv::Writer::write_record, 1
Current time, 1
Current working directory, 1
Curve25519, 1, 2
Custom deserializer, 1
Custom event formatter, 1
Custom logger, 1
Custom logger configuration, 1
Dangling references, 1
Dashboard, 1
dashmap, 1, 2, 3, 4, 5, 6
Data encoding, 1
Data parallelism, 1
Data races, 1
Data structures, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35
data-encoding, 1, 2, 3, 4, 5
Database, 1, 2, 3
Database implementations, 1
Database interfaces, 1, 2, 3, 4
Database management systems, 1
Databases, 1, 2, 3, 4, 5, 6, 7, 8
datafusion, 1
Date, 1, 2
date, 1
Date and time, 1, 2, 3, 4, 5, 6, 7
DateTime, 1
deadpool, 1
Debug message, 1
Debug output, 1
Debugging, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15
Debugging information, 1
Decimal, 1
Decode, 1
Decoding, 1
Dedicated thread, 1, 2, 3
Default implementation, 1
Delimiter, 1
Dependencies, 1, 2
der, 1, 2
Dereference operator, 1
Derivable traits, 1
Derive, 1, 2, 3, 4
derive, 1, 2
derive_builder, 1
derive_more, 1, 2, 3, 4, 5, 6
Deserialization, 1
Deserialize, 1
desktop-lite, 1
Destructuring, 1
Detect loops for a given path, 1
Dev Container, 1, 2
Dev Containers, 1
Development, 1
Development tools, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33
Development tools::Build Utils, 1
Development tools::Debugging, 1
devx, 1
devx-cmd, 1
devx-pre-commit, 1
diesel, 1, 2
Digest, 1, 2, 3
digest, 1, 2, 3
digest::Context, 1
digest::Digest, 1
directories, 1
Directory, 1, 2, 3, 4
dirs, 1
Distributions, 1
Diverging functions, 1
Django, 1
docker, 1, 2, 3
docker compose, 1
Dockerfile, 1
Docopt, 1
docs.rs, 1, 2, 3
Document, 1
Documentation, 1
Documentation comments, 1
Dotenv, 1
dotenv, 1, 2, 3
dotenvy, 1, 2, 3
Doxygen, 1
druid, 1, 2
dsa, 1, 2
duct, 1, 2, 3
Duplicate filenames, 1
Duration, 1
dyn, 1
dyn-clone, 1, 2, 3, 4
Ecc, 1, 2, 3, 4, 5, 6
ecdsa, 1, 2
Ed25519, 1
ed25519, 1, 2
ed25519-dalek, 1
egui, 1, 2, 3
Elapsed time, 1
elasticsearch, 1, 2, 3, 4
Email address, 1
embassy, 1, 2, 3
Embedded development, 1, 2, 3
Encode, 1
Encoding, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39
Encryption, 1, 2, 3
Endian, 1
Enums, 1, 2, 3
Env, 1, 2, 3
env_logger, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12
env_logger::Builder, 1, 2
env_logger::Builder::format, 1
env_logger::Builder::init, 1
env_logger::Builder::parse, 1
env_logger::Builder::target, 1
env_logger::fmt::Target, 1
env_logger::init, 1
Environment, 1, 2
Environment variables, 1, 2
envy, 1, 2, 3
Equal, 1
Error, 1, 2
Error handling, 1
Error-handling, 1, 2
error_chain, 1
Events, 1
exa, 1, 2, 3
Example code, 1
Executables, 1
External command, 1, 2
External FFI bindings, 1
eyre, 1, 2, 3
F32, 1
f32, 1
F64, 1
f64, 1
fancy-regex, 1
Fast, 1
fastrand, 1
Feature-unification, 1
FFI, 1
Fields, 1
File, 1, 2
File sizes, 1
File systems, 1
Files, 1
Filesystem, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18
Finance, 1
Financial, 1
Firefox, 1
Fixed, 1
Flag, 1
Flags, 1
flagset, 1, 2
flate2, 1, 2, 3, 4, 5, 6
flate2::read::GzDecoder, 1
flate2::write::GzEncoder, 1, 2
floem, 1
flume, 1
flurry, 1
fn, 1
Folder, 1, 2
form-urlencoded, 1
form_urlencoded::byte_serialize, 1
form_urlencoded::parse, 1
Formal methods, 1
Format, 1
Formatter, 1
Framework, 1, 2, 3
FromStr, 1
Function, 1
Functions, 1
Future, 1, 2, 3, 4
Futures, 1, 2, 3, 4, 5, 6, 7, 8, 9
futures, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14
futures::future::Select, 1
futures::prelude::stream::StreamExt::filter, 1
futures::prelude::stream::StreamExt::fold, 1
futures::prelude::stream::StreamExt::for_each_concurrent, 1
futures::prelude::stream::StreamExt::map, 1
futures::prelude::stream::TryStreamExt::try_filter, 1
futures::prelude::stream::TryStreamExt::try_fold, 1
futures::prelude::stream::TryStreamExt::try_for_each_concurrent, 1
futures::stream::Stream, 1
futures_executor, 1, 2, 3, 4
futures_executor::block_on, 1
Game development, 1
Game engines, 1
Garbage, 1, 2, 3
Garbage collection, 1
Garbage collector, 1
Gcm, 1
Generator, 1
Generics, 1, 2
Ghash, 1
Git, 1, 2
Gitbook, 1
Github actions, 1
GitHub API, 1
GitHub API - Rate limiting, 1
Gitignore, 1
glidesort, 1, 2
Glob, 1, 2
glob, 1, 2, 3, 4, 5, 6
glob::glob_with, 1
glob::glob_with::glob_with, 1
glob::MatchOptions, 1
Global mutable state, 1
globset, 1
Gmp, 1
Golang, 1, 2, 3
gping, 1, 2
Grapheme, 1
Graphemes, 1
Graphics, 1
gRPC, 1
GUI, 1, 2
GUI manager, 1
Guppy, 1
Hardware, 1
Hardware support, 1, 2
Hash, 1, 2, 3
Hashing, 1, 2
Hashmap, 1, 2, 3
Hashtags, 1
Header, 1
Heap, 1
Heap allocations, 1
helix, 1
Hex, 1, 2
Hexadecimal representation, 1
Hierarchical, 1
Hook, 1
Hour/minute/second, 1
href, 1
Html, 1, 2
HTML document, 1
Http, 1, 2, 3, 4
http, 1, 2, 3, 4
HTTP authentication, 1
HTTP client, 1, 2, 3, 4, 5, 6, 7, 8, 9
HTTP GET request, 1
HTTP response, 1
HTTP server, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17
http-body-util, 1
hyper, 1, 2, 3, 4, 5, 6, 7, 8, 9
hyper::header!, 1, 2
hyper::header::AUTHORIZATION, 1
hyper::header::USER_AGENT, 1
i128, 1
i16, 1
i32, 1
i64, 1
i8, 1
iced, 1, 2
if let, 1
ignore, 1, 2, 3
image, 1, 2, 3, 4, 5, 6
image::DynamicImage::resize, 1
image::ImageBuffer::new, 1
image::ImageBuffer::put_pixel, 1
image::ImageBuffer::save, 1
image::Rgb::from_channels, 1
Images, 1
Immutable type, 1
indexmap, 1
indicatif, 1, 2, 3, 4
infisearch, 1, 2, 3
Inner value, 1
Inode, 1
inquire, 1
Interfaces, 1
Interior mutability, 1
Intermediate representation, 1
Internationalization (i18n), 1
Interpreters, 1
Invalid data, 1
Io, 1, 2, 3, 4, 5
isize, 1, 2
ISO 8601, 1
Iterator, 1, 2, 3, 4
Iterators, 1
iterators, 1
itertools, 1, 2, 3, 4, 5
Itu, 1
Jinja2, 1
Join, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11
join!, 1
JPEG, 1
JSON, 1
just, 1, 2, 3, 4, 5, 6, 7, 8
kanal, 1, 2, 3, 4, 5
kani, 1, 2, 3, 4
Key, 1, 2
Key crates, 1
Keys, 1
L1 norm, 1, 2, 3
L2 norm, 1, 2, 3
Lazy, 1
Lazy static, 1
lazy_static, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12
len, 1
lens, 1
lens_rs, 1, 2
leptos, 1, 2, 3
lexopt, 1
lib.rs, 1, 2, 3, 4, 5
libc, 1
Library crate, 1
Libsodium, 1
Lifetimes, 1, 2
Line editing, 1
linfa, 1, 2, 3, 4
Links, 1
Lint checks, 1
Little-endian, 1
Lock-free, 1, 2, 3, 4
loco_rs, 1, 2
Log, 1, 2, 3
log, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14
Log configuration, 1
Log levels, 1, 2
Log output to a custom location, 1
Log verbosity, 1
Log4, 1
log4rs, 1, 2, 3, 4, 5
log4rs::append::file::FileAppender, 1
log4rs::config::Config, 1
log4rs::encode::pattern, 1
log::debug, 1
log::error, 1
log::Level, 1
log::LevelFilter, 1
log::Log, 1, 2
log::Record::args, 1
log::Record::level, 1
Logger, 1, 2
Logging, 1, 2, 3, 4, 5
Logging utilities, 1
Logical cpu cores, 1
lru, 1, 2, 3
lsd, 1, 2, 3
Macro, 1, 2, 3
Macros, 1, 2, 3
main, 1, 2
make, 1
Map, 1
mapv, 1
Markdown, 1, 2
Markup, 1
match, 1, 2
Math, 1
Mathematics, 1, 2, 3, 4, 5, 6, 7, 8
md-5, 1, 2
Md5, 1
Mdbook, 1, 2, 3, 4, 5
mdbook, 1, 2, 3, 4, 5, 6, 7
mdbook-cmdrun, 1, 2
mdbook-hide, 1, 2, 3
mdbook-journal, 1, 2, 3, 4
mdbook-keeper, 1, 2
mdbook-linkcheck, 1, 2, 3
mdbook-pagetoc, 1, 2
mdbook-private, 1, 2
mdbook-tera, 1, 2, 3, 4
mdbook-theme, 1, 2
mdbook-toc, 1, 2
mdbook-yapp, 1
Meetups, 1
meilisearch, 1
memmap2, 1, 2, 3
memmap2::Mmap::map, 1
Memory, 1
Memory leaks, 1
Memory management, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11
Memory map, 1
Memory mapping, 1
Memset, 1
Message, 1, 2, 3
Message passing, 1, 2, 3
Messagepack, 1
Messages, 1, 2
Methods, 1
miette, 1, 2, 3
mime, 1, 2, 3, 4, 5, 6, 7, 8
MIME type, 1, 2
mime::Mime, 1, 2, 3, 4, 5
minisearch, 1, 2
mio, 1, 2, 3, 4
MIR, 1
miri, 1, 2
mod, 1
Modules, 1
mongodb, 1, 2, 3, 4, 5
monostate, 1, 2, 3, 4, 5, 6
move, 1
Mpmc, 1, 2, 3, 4, 5
MPSC, 1
Mpsc, 1
Msgpack, 1
MSSQL, 1
Multi-consumer channels, 1
multimap, 1
Multiple, 1
Multiple owners, 1, 2
Multithreaded runtimes, 1
Multithreading, 1
Mutable references, 1
Mutex, 1, 2, 3
MySQL, 1
Nacl, 1
nalgebra, 1, 2, 3, 4, 5, 6, 7
nalgebra::Matrix3, 1
Named capture groups, 1
native-tls, 1
native-windows-gui, 1
ndarray, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14
ndarray::arr1, 1
ndarray::arr2, 1, 2, 3
ndarray::Array, 1, 2, 3, 4
ndarray::Array1, 1, 2
ndarray::ArrayBase::dot, 1, 2
ndarray::ArrayBase::fold, 1, 2
ndarray::ArrayView1, 1, 2
Network programming, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35
Networking applications, 1
Newsletter, 1
Newtype pattern, 1
Nist, 1, 2
No dynamic allocation, 1, 2, 3, 4
No standard library, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53
No-std, 1
No_std, 1, 2, 3, 4
nom, 1, 2
Non-blocking, 1, 2, 3, 4, 5, 6, 7
Non-blocking I/O operations, 1
Non-sequential reads, 1
noplayground, 1
notify, 1, 2, 3, 4
nu-ansi-term, 1
null, 1
Null pointer dereferences, 1
num, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
num-bigint, 1
num-cpus, 1
num-traits, 1
num::BigInt, 1
num::complex::Complex, 1, 2
num_cpus, 1, 2, 3, 4
num_cpus::get, 1, 2, 3
Number, 1
Numerics, 1, 2, 3, 4
nvm, 1
OAuth, 1
Object-safe traits, 1
Once, 1
once_cell, 1, 2, 3, 4, 5
once_cell::sync::Lazy, 1
OnceCell, 1
Online books, 1
open, 1, 2
opencv, 1
openobserve, 1
openrr, 1, 2
Operating system threads, 1
Operating system-specific APIs, 1
Operating systems, 1, 2, 3
Options, 1
Ord, 1
ordered-float, 1
OS, 1, 2, 3, 4, 5, 6, 7, 8
Output coloring and formatting, 1
Output format, 1
Overflow, 1
Ownership, 1, 2
owo-colors, 1
Package, 1
Pagetoc, 1
Paint, 1
Parallel, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
Parallel execution, 1
Parallel pipeline, 1
Parallel programming, 1, 2
Parallel sort, 1
Parallel tasks, 1
Parameters, 1
Parent module, 1
parking_lot, 1, 2, 3, 4, 5, 6
parking_lot::Condvar, 1
parking_lot::Mutex, 1
parking_lot::Once, 1
parking_lot::ReentrantMutex, 1
parking_lot::RwLock, 1
Parser, 1
Parser implementations, 1, 2, 3, 4, 5, 6, 7, 8
Parsing, 1
Parsing tools, 1, 2, 3, 4
Password, 1
paste, 1, 2, 3, 4
Path, 1, 2
Pattern, 1, 2, 3
Pattern matching, 1
PBKDF2, 1
Pem, 1
pem-rfc7468, 1, 2
percent_encoding, 1, 2, 3
percent_encoding::percent_decode, 1
percent_encoding::utf8_percent_encode, 1
Performance, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
pest, 1, 2
Phone numbers, 1
pico-args, 1
Piped external commands, 1
Pkcs, 1, 2, 3
pkcs8, 1, 2
plotly, 1
Plugin, 1
Podcasts, 1
polars, 1
Pool, 1
postage, 1, 2, 3
postgres, 1, 2, 3, 4, 5, 6
postgres::Client, 1, 2
postgres::Client::connect, 1
postgres::Client::execute, 1
postgres::Client::query, 1
PostgreSQL, 1
Postgresql, 1
powi, 1
Pre-commit, 1
Pre-release, 1
Precision, 1
Preprocessor, 1, 2, 3
Pretty print, 1
Primitives, 1
Private, 1
Private by default, 1
Private items, 1
proc-macro2, 1, 2
Procedural macro helpers, 1
Process, 1, 2
Producer, 1, 2
Production-ready code, 1
Profile, 1
Profiling, 1
Progress bars and spinners, 1
Promises, 1
prost, 1
Protobuf, 1
protobuf, 1
pub, 1
Pull request, 1
pyenv, 1
pyo3, 1, 2, 3
pyoxidizer, 1
Query, 1
Queue, 1
quickinstall, 1
quote, 1, 2
r3bl_tuify, 1, 2, 3, 4
Rand, 1, 2, 3, 4
rand, 1, 2, 3, 4, 5
rand::distributions::Alphanumeric, 1
rand::distributions::Distribution, 1
rand::distributions::Distribution::sample, 1
rand::distributions::Standard, 1
rand::distributions::uniform::Uniform, 1
rand::Rng, 1, 2
rand::Rng::gen_range, 1, 2
rand_distr, 1, 2, 3, 4
rand_distr::Normal, 1
Random, 1
Random numbers, 1, 2
Random passwords, 1
Random value, 1
Random-number generator, 1
Range, 1
ratatui, 1, 2, 3
rayon, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21
rayon::iter::IntoParallelRefIterator::par_iter, 1
rayon::iter::IntoParallelRefIterator::par_iter_mut, 1
rayon::iter::ParallelIterator::any, 1, 2, 3, 4
rayon::iter::ParallelIterator::filter, 1, 2
rayon::iter::ParallelIterator::find_any, 1, 2, 3
rayon::iter::ParallelIterator::map, 1, 2
rayon::iter::ParallelIterator::reduce, 1, 2
rayon::iter::ParallelIterator::sum, 1
rayon::join, 1, 2
rayon::slice::ParallelSliceMut::par_sort_unstable, 1
rayon::spawn, 1
Rc
Rcu, 1, 2, 3
Read/write lock, 1
Recursive, 1, 2, 3, 4
redis, 1, 2
redox, 1
Reference counting, 1
References, 1
Regex, 1
regex, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11
regex::Regex, 1
regex::Regex::captures_iter, 1, 2
regex::Regex::replace_all, 1
regex::RegexSetBuilder, 1, 2
regex::Replacer, 1
Regular expressions, 1
Relative path, 1
Rendering, 1, 2
Rendering engine, 1
Replace, 1
Replacement string syntax, 1
Request, 1
reqwest, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25
reqwest::blocking::Client::get, 1
reqwest::blocking::Client::head, 1
reqwest::blocking::get, 1
reqwest::blocking::Response, 1
reqwest::Client, 1, 2, 3, 4, 5, 6, 7
reqwest::Client::head, 1
reqwest::Client::post, 1, 2, 3
reqwest::ClientBuilder::build, 1
reqwest::ClientBuilder::timeout, 1
reqwest::Error, 1
reqwest::get, 1, 2, 3, 4, 5, 6
reqwest::header, 1
reqwest::header::HeaderMap::get, 1
reqwest::header::HeaderValue, 1
reqwest::RequestBuilder, 1
reqwest::RequestBuilder::basic_auth, 1
reqwest::RequestBuilder::body, 1
reqwest::RequestBuilder::header, 1
reqwest::RequestBuilder::send, 1, 2, 3
reqwest::Response, 1, 2
reqwest::Response::json, 1
reqwest::Response::url, 1
reqwest::Result, 1
reqwest::StatusCode, 1
reqwest::StatusCode::FORBIDDEN, 1
Return type, 1
Return-position impl Trait
in traits, 1
RFC-2822 format, 1
RFC-3339, 1
RFC-3339 format, 1
RFC-7233, 1
rhai, 1, 2, 3, 4, 5
riker, 1
ring, 1, 2, 3, 4, 5, 6, 7
ring::hmac, 1
ring::pbkdf2, 1
ring::pbkdf2::derive, 1
ring::pbkdf2::verify, 1
ring::rand::SecureRandom::fill, 1
ring::signature::Signature, 1
rmp-serde, 1
Robotics, 1
rocket, 1, 2
roogle, 1, 2
roxygen, 1
Rsa, 1, 2, 3, 4
rsa, 1, 2
ruff, 1
rug, 1
rui, 1, 2
Runcmd, 1
Runner, 1
Runtime, 1
rusqlite, 1, 2, 3, 4, 5, 6
rusqlite::Connection, 1, 2
rusqlite::Connection::execute, 1
rusqlite::Connection::last_insert_rowid, 1
rusqlite::Connection::open, 1, 2, 3
rusqlite::Connection::prepare, 1
rusqlite::Connection::transaction, 1
rusqlite::Statement, 1, 2
rusqlite::Statement::query_map, 1
rusqlite::Transaction::commit, 1
Rust, 1
Rust analyzer, 1
Rust binaries installation, 1
Rust book, 1
Rust by example, 1
Rust code examples, 1
Rust installation, 1
Rust interpreter, 1
Rust learning, 1
Rust on Nails, 1
Rust patterns, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25
Rust playground, 1, 2, 3
Rust starter pack, 1
Rust tooling, 1, 2
Rust tools, 1
rust_cache, 1
rust_decimal, 1
RUST_LOG, 1, 2, 3, 4
Rustbook, 1
rustc, 1, 2, 3
rustdesk, 1
rustdoc, 1, 2
rustfix, 1
Rustfmt, 1
rustfmt, 1, 2, 3
rustfmt-nightly, 1
rustfmt.toml, 1
rustfmt::skip, 1
rustfmt::skip::attributes(custom_attribute), 1
rustfmt::skip::macros(macro_name), 1
rustls, 1
rustquant, 1, 2, 3, 4, 5, 6
rustup, 1, 2
Rwlock, 1
salsa, 1
Salted passwords, 1
Same, 1
same-file, 1, 2
same_file::Handle, 1
same_file::is_same_file, 1
sccache, 1
Science, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23
science, 1
Scope, 1
sea-orm, 1, 2, 3
seaography, 1, 2, 3
Searching, 1
Secp256k1, 1
Secure, 1
Security, 1
Select, 1, 2, 3
select, 1, 2, 3
select::document::Document::find, 1
select::document::Document::from_read, 1
select::node::Node::attr, 1, 2, 3
select::predicate::Name, 1
select::selection::Selection, 1
Semantic Versioning Specification, 1
Semantic versioning specification, 1
semver, 1, 2, 3, 4, 5, 6, 7, 8, 9
semver::Version, 1, 2, 3, 4, 5
semver::Version::parse, 1, 2, 3
semver::VersionReq, 1, 2
semver::VersionReq::matches, 1, 2
Send, 1, 2
Serde, 1, 2, 3
serde, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15
serde-ignored, 1
serde::Deserialize, 1, 2
serde_json, 1, 2, 3, 4, 5, 6, 7
serde_json::from_str, 1
serde_json::json, 1
serde_json::to_string, 1, 2
serde_json::Value, 1
Serialization, 1, 2, 3
Serialize, 1
Serialize custom structs, 1
Service, 1, 2
Set, 1
Setter, 1, 2
Settings, 1, 2
SHA-256, 1
sha1, 1, 2, 3
sha2, 1, 2, 3
SHA256, 1
Shadowing, 1
Shared memory concurrency, 1
Shared state, 1
Shell, 1
shields.io, 1
Short-lived thread, 1
Shutdown, 1
shuttle.rs, 1
Signature, 1, 2, 3, 4, 5
Signing, 1
Simple, 1
Simulation, 1
Simultaneous references, 1
sin, 1
Single producer, single consumer, 1
Single-threaded runtime, 1
Siv, 1
sled, 1, 2, 3, 4, 5, 6
Slices, 1
slint, 1, 2, 3, 4
slog, 1
slotmap, 1, 2, 3, 4, 5
smallvec, 1
Smart pointers, 1, 2
smol, 1, 2, 3, 4, 5, 6
sodiumoxide, 1
Sort, 1
sort, 1
Sorting, 1, 2, 3
Span::enter, 1
spawn_blocking, 1, 2, 3
Spawned threads, 1
Spawning tasks, 1
Splay tree, 1
split_at, 1
Spmc, 1
Sql, 1
SQLite, 1
sqlx, 1, 2, 3
sqrt, 1, 2
src, 1
src/lib.rs, 1
src/main.rs, 1
Stack, 1
stakker, 1, 2, 3, 4
starship, 1, 2, 3, 4
Static, 1
staticlib, 1
std, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33
std-core::iter::Iterator::filter_map, 1
std::borrow::Borrow, 1
std::borrow::Cow, 1
std::borrow::Cow::into_owned, 1
std::borrow::ToOwned, 1
std::boxed::Box, 1
std::cell::Cell, 1
std::clone::Clone, 1
std::cmp::Eq, 1
std::cmp::Ord, 1
std::cmp::Ord::cmp, 1
std::cmp::Ordering, 1
std::cmp::PartialEq, 1
std::cmp::PartialOrd, 1
std::cmp::PartialOrd::partial_cmp, 1
std::collections::hash_map::Entry, 1
std::collections::HashMap, 1, 2, 3, 4
std::convert::AsMut, 1
std::convert::AsRef, 1, 2
std::convert::From, 1
std::default::Default, 1, 2
std::env::current_dir, 1
std::env::var, 1
std::error::Error, 1, 2
std::fmt, 1
std::fmt::Debug, 1
std::fmt::Display, 1
std::fs::DirEntry::path, 1
std::fs::File, 1, 2, 3, 4, 5
std::fs::File::create, 1
std::fs::File::open, 1
std::fs::File::seek, 1
std::fs::File::try_clone, 1
std::fs::Metadata, 1
std::fs::Metadata::is_file, 1
std::fs::Metadata::modified, 1
std::fs::read_dir, 1
std::future::Future, 1, 2
std::io::BufRead::lines, 1
std::io::BufReader, 1, 2
std::io::copy, 1
std::io::Error, 1
std::io::Lines, 1
std::io::Read, 1
std::io::Read::read_to_string, 1, 2
std::io::Stderr, 1, 2
std::io::Stdout, 1, 2, 3
std::iter::Iterator::all, 1
std::iter::Iterator::any, 1
std::iter::Iterator::filter, 1
std::iter::Iterator::find, 1
std::iter::Iterator::fold, 1
std::iter::Iterator::max_by_key, 1
std::iter::Iterator::sum, 1, 2
std::marker::Copy, 1
std::marker::Send, 1
std::marker::Sync, 1
std::marker::Unpin, 1
std::ops::Deref, 1, 2, 3
std::ops::Drop, 1, 2
std::ops::Fn, 1
std::ops::FnMut, 1
std::ops::FnOnce, 1
std::ops::FromResidual, 1
std::option::Option, 1, 2, 3, 4, 5, 6
std::option::Option::as_deref, 1
std::option::Option::as_deref_mut, 1
std::option::Option::expect, 1
std::option::Option::None, 1, 2, 3, 4
std::option::Option::Some, 1
std::option::Option::unwrap, 1
std::option::Option::unwrap_or, 1
std::option::Option::unwrap_or_default, 1
std::option::Option::unwrap_or_else, 1
std::panic::RefUnwindSafe, 1
std::panic::UnwindSafe, 1
std::path::Path::strip_prefix, 1
std::process::Command, 1, 2, 3, 4, 5
std::process::Output, 1, 2
std::process::Stdio, 1
std::result::Result::unwrap_or, 1
std::str::FromStr, 1
std::string::String, 1, 2, 3, 4, 5
std::string::ToString, 1
std::sync::Arc, 1
std::sync::atomic, 1
std::sync::atomic::AtomicBool, 1
std::sync::atomic::AtomicI8, 1
std::sync::atomic::AtomicIsize, 1
std::sync::atomic::AtomicU16, 1
std::sync::atomic::AtomicUsize, 1
std::sync::mpsc, 1
std::sync::mpsc::channel, 1
std::sync::mpsc::Receiver::recv, 1
std::sync::Mutex, 1, 2, 3, 4, 5
std::sync::MutexGuard, 1
std::sync::RwLock, 1
std::task::Poll, 1
std::thread::spawn, 1
std::time::Duration, 1
std::time::Duration::as_secs, 1
std::time::Instant, 1
std::time::Instant::elapsed, 1, 2
std::time::Instant::now, 1
std::time::SystemTime::elapsed, 1
std::vec::Vec::sort, 1
std::vec::Vec::sort_by, 1
std::vec::Vec::sort_unstable, 1
stdout, 1
stdx, 1
stork-search, 1, 2, 3
Stream, 1
Strings, 1
Strip, 1
Struct, 1
structopt, 1
Structs, 1, 2
Structured, 1
stylish, 1
Subcommand, 1, 2
Submodules, 1
subtle, 1
SUMMARY.md, 1
swc_ecma_parser, 1
Symbolic links, 1
syn, 1, 2, 3
Sync}{{hi:'static, 1
Synchronization, 1
Synchronous code, 1, 2
Synchronous IO, 1
syslog, 1, 2, 3
syslog::init, 1, 2, 3
Table, 1
tantivy, 1, 2, 3
tar, 1, 2, 3, 4
tar::Archive::entries, 1
tar::Archive::unpack, 1
tar::Builder, 1
tar::Builder::append_dir_all, 1
tar::Entry, 1
tar::Entry::unpack, 1
Task, 1
tauri, 1, 2, 3
Tempfile, 1
tempfile, 1, 2, 3, 4
tempfile::Builder, 1
tempfile::Builder::tempdir, 1
Template, 1, 2, 3
Template engine, 1, 2, 3, 4, 5
Template engines, 1, 2
Tera, 1
tera, 1
termbook, 1, 2
termcolor, 1
Terminal, 1, 2, 3, 4, 5, 6, 7
termion, 1
Testing, 1, 2, 3, 4
Text, 1, 2
Text editors, 1, 2
Text processing, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15
Theme, 1
thiserror, 1, 2, 3, 4, 5, 6
Thread, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11
Thread pools, 1, 2, 3, 4
threadpool, 1, 2, 3, 4, 5
threadpool::ThreadPool, 1
threadpool::ThreadPool::execute, 1
Threads, 1, 2
Tickers, 1
Time, 1
time, 1, 2
Time since last modification, 1
Timeouts, 1
Timestamp, 1
timezones, 1
tinysearch, 1, 2
tinytemplate, 1
tinyvec, 1
to_radians, 1
toasty, 1
Toc, 1
Tokio, 1
tokio, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17
Tokio examples, 1
tokio-graceful-shutdown, 1, 2, 3
Tokio-postgres, 1
tokio-postgres, 1
tokio::main, 1, 2
tokio::main::current-thread-runtime, 1
tokio::spawn, 1
tokio::sync::oneshot, 1
tokio::task::LocalSet, 1
tokio::task::spawn, 1, 2
tokio::task::spawn_blocking, 1, 2
tokio_graceful_shutdown, 1, 2
TOML, 1
toml, 1, 2, 3, 4, 5, 6
tonic, 1, 2, 3, 4, 5, 6
tower, 1, 2, 3, 4, 5, 6, 7
tower-http, 1, 2, 3, 4
tower::Layer, 1, 2, 3
tower::Service, 1, 2
tower::ServiceBuilder, 1
tower_http, 1, 2, 3
Tracing, 1, 2
tracing, 1, 2, 3, 4, 5, 6
tracing-subscriber, 1, 2, 3
tracing::span::Span::in_scope, 1
tracing_journald, 1
tracing_subscriber, 1, 2
Trait, 1
Trait objects, 1
Traits, 1, 2, 3
trillium, 1, 2, 3, 4, 5
Tty, 1
Tui, 1
tui, 1, 2, 3
typed-builder, 1
typesense, 1, 2
u128, 1
u16, 1
u32, 1
u8, 1, 2
udeps, 1
Unicode, 1
unicode-segmentation, 1
unicode_segmentation, 1, 2
unicode_segmentation::UnicodeSegmentation::graphemes, 1
Uniform distribution, 1, 2
Unit type, 1
Unsized, 1
Unused variable, 1
ureq, 1
URL, 1
url, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14
url::ParseOptions, 1
url::PathSegmentsMut::clear, 1
url::Position, 1
url::Url, 1, 2, 3, 4
url::Url::join, 1
url::Url::origin, 1
url::Url::parse, 1
url::Url::parse_with_params, 1
url::Url::set_query, 1
use, 1
usize, 1, 2
UTC, 1
UTF-8, 1
Utilities, 1
Utility, 1
uuid, 1
Value formatting, 1, 2, 3
Values, 1
Variables, 1, 2
Variants, 1
Vec, 1
Vector, 1, 2
Vectors, 1
Version number, 1, 2
Version requirements, 1
Version string, 1
Visibility, 1
Volatile, 1
VS code, 1, 2, 3
Vtable, 1
Walk, 1, 2, 3, 4
walkdir, 1, 2, 3, 4, 5, 6, 7
walkdir::IntoIter::filter_entry, 1, 2
walkdir::WalkDir::follow_links, 1
walkdir::WalkDir::max_depth, 1
walkdir::WalkDir::min_depth, 1
walkdir::WalkDir::new, 1, 2
wasmtime, 1
Watch, 1
watchmaker, 1
watt, 1, 2, 3
Web, 1, 2
Web programming, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25
WebAssembly, 1, 2, 3, 4
WebSocket, 1, 2
wgpu, 1
while let, 1
Win, 1
Wincon, 1
Windows, 1
windows, 1, 2
Windows APIs, 1
Word, 1
Work-stealing runtime, 1
Workspace-hack, 1
wrapping_add, 1
Wyrand, 1
x509-cert, 1, 2
Xdg, 1, 2
xilem, 1, 2, 3, 4, 5
xshell, 1, 2, 3, 4, 5
xsv, 1
yansi, 1
year/month/day/weekday, 1
yew, 1, 2, 3, 4
zed, 1, 2, 3
zenoh, 1, 2
Zero, 1
zerocopy, 1
zeroize, 1
Zig, 1
zola, 1, 2
Thanks
This reference guide is written and edited by John CD.
It is the successor of and incorporates most of the Rust Cookbook. Thanks to its many contributors.