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, recipes, and links. It is intended to be everything you need for day-to-day Rust coding, in one place.
It quickly summarizes the basics of the language and often-used elements of the standard library.
It then focuses on cross-cutting concerns that affect most aspects of development e.g. error handling, error customization, configuration, p-debugging...
Concurrency, including asynchronous programming, is covered in details.
Next are tools, such as Cargo, Clippy, Rustfmt, as well as links and examples specific to programming domains such as CLI and Web development. The links section provides pointers to notable Rust websites, books, and code examples.
Who should read this book?
This book is intended both for new Rust programmers (to get an overview of the capabilities of the Rust ecosystem and pointers to other resources) and for 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.
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 almost more than 100 thousand crates 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 (not yet) dominant frameworks or platforms like Rails
, Django
, Spring
or Node
in the Rust world.
This book intends to provide EXAMPLES to demonstrate the use of key Rust crates, examples which are absent from or scattered in the reference docs⮳, and hopes to become a "cheat sheet on steroid" for the Rust ecosystem (not just for the Rust language).
What other books should I consult?
The Rust cookbook⮳ demonstrate good practices to accomplish common programming tasks, using the crates of the Rust ecosystem. It focuses mainly on std
and a few core crates. The Rust Cookbook is a collection of simple examples that demonstrate good practices to accomplish common programming tasks, using the crates of the Rust ecosystem.
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 libraries.
Index of Examples
Accessibility
Screen Readers
Recipe | Crates | Categories |
---|---|---|
Make a user interface accessible to screen readers |
Algorithms
Randomness
Sorting
Recipe | Crates | Categories |
---|---|---|
Sort a Vector of Integers | ||
Sort a Vector of Floats | ||
Sort a Vector of Structs |
API Bindings
Python Interop
Recipe | Crates | Categories |
---|---|---|
Python Interop |
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 |
---|---|---|
Basic Authentication |
Caching
LRU Caching
Recipe | Crates | Categories |
---|---|---|
LRU cache |
Command Line Interface
ANSI Terminal
Recipe | Crates | Categories |
---|---|---|
ANSI Terminal | ||
Printing colored text to the Terminal | ||
Bold text in Terminal | ||
Bold and colored text in terminal | ||
Manipulate the cursor, style the output, handle input events |
Arguments
Recipe | Crates | Categories |
---|---|---|
Using clap's builder API | ||
Using clap 's derive API | ||
lexopt | ||
pico-args |
TUI
Recipe | Crates | Categories |
---|---|---|
Build complex TUI | {{hi:Command-line interface}} |
User Interaction
Recipe | Crates | Categories |
---|---|---|
Ask for confirmation, selection, text input | ||
Display progress bars and spinners | {{hi:indicatif}} |
Command Line Utilities
Filesystem
Networking
Recipe | Crates | Categories |
---|---|---|
Networking tools: gping |
Shells
Recipe | Crates | Categories |
---|---|---|
Shells and related utilities |
Compilers
Cross Compilation
Recipe | Crates | Categories |
---|---|---|
Cross-compile for multiple target OSes and architectures |
Faster Linking
Recipe | Crates | Categories |
---|---|---|
Faster linking | ||
Alternative - Mold linker |
Compilation Duration Reduction
Recipe | Crates | Categories |
---|---|---|
Use dynamic Linking | ||
Compile incrementally | ||
Measuring build times | ||
Change optimization levels | ||
Compilation duration reduction | ||
Incremental computation |
Compression
tar
Recipe | Crates | Categories |
---|---|---|
Decompress a tarball | ||
Compress a directory into a tarball | ||
Decompress a tarball while removing a prefix from the paths |
Computer Vision
OpenCV
Recipe | Crates | Categories |
---|---|---|
Analyze images with Open CV |
Shared State
Concurrent Data Structures
Recipe | Crates | Categories |
---|---|---|
Dashmap | ||
Bounded Multi-producer Multi-consumer Queue | ||
flurry |
Shared State
Recipe | Crates | Categories |
---|---|---|
Mutexes | ||
Parking Lot | ||
Atomics | ||
arc-swap |
Config
Configuration
Recipe | Crates | Categories |
---|---|---|
Configuration Management with Config | ||
Configuration Management with Confy |
Environment Variables
Recipe | Crates | Categories |
---|---|---|
Read environment variables from a .env file | ||
Retrieve a single environment variable | ||
Deserialize environment variables into type-safe structs |
Cryptography
Encryption
Recipe | Crates | Categories |
---|---|---|
Salt and hash a password with PBKDF2 |
Hashing
Password Hashing
Data Structures
Bitfield
Recipe | Crates | Categories |
---|---|---|
Define and operate on a type represented as a bitfield | ||
Flagset |
Maps
Recipe | Crates | Categories |
---|---|---|
Insertion-ordered map | ||
Multimap | ||
Slotmap |
Stack-allocated Arrays
UUIDs
Recipe | Crates | Categories |
---|---|---|
Generate and parse UUIDs |
Database Implementations
Databases
Recipe | Crates | Categories |
---|---|---|
Sled | ||
SurrealDB | [] cat-database-implementations |
Search
Recipe | Crates | Categories |
---|---|---|
Meilisearch | ||
Tantivy |
Date and Time
Duration
Recipe | Crates | Categories |
---|---|---|
Measure elapsed time | ||
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 & Friends
Cargo
Recipe | Crates | Categories |
---|---|---|
Basic Cargo Usage | ||
Cargo.toml and lock files |
Crate Registries
Recipe | Crates | Categories |
---|---|---|
Crate Registries |
Package Layout
Recipe | Crates | Categories |
---|---|---|
Package layout |
Documentation
Badges
Documentation
Recipe | Crates | Categories |
---|---|---|
Documenting your code | ||
Module or crate-level documentation |
mdbook
Recipe | Crates | Categories |
---|---|---|
mdBook | ||
mdbook plugins | ||
CD / CI | ||
Rust playground |
Formatting
Formatting
Recipe | Crates | Categories |
---|---|---|
Rustfmt | ||
Rustfmt Configuration | ||
Formatting attributes |
Installation
Installation
Recipe | Crates | Categories |
---|---|---|
Rustup | ||
Cargo install | ||
Cargo binstall |
rustup
Recipe | Crates | Categories |
---|---|---|
Key commands |
Other
just
Recipe | Crates | Categories |
---|---|---|
Installation in a dev container | ||
Example justfile |
miri
Recipe | Crates | Categories |
---|---|---|
Miri | ||
Miri installation |
Other
Recipe | Crates | Categories |
---|---|---|
Background code checker | ||
API search | roogle{{hi:roogle}} | |
Deployment on shuttle.rs | shuttle.rs | |
Binary minimizer | ||
Code generators | ||
Code verifier | ||
Formal Methods |
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 | ||
Devx | ||
xshell: Making Rust a Better Bash |
Code Formatting and Linting
Recipe | Crates | Categories |
---|---|---|
Format your code | ||
Format | ||
Lint your code | ||
Fix compiler warnings | ||
Check your code before committing it |
Cross-Compiling
Recipe | Crates | Categories |
---|---|---|
Cross-compile using Zig as the linker |
Maintaining
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 |
---|---|---|
Templates | Cargo Generate | |
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 |
---|---|---|
Older alternatives to tracing | ||
log | ||
slog | ||
log4rs | ||
env_logger | ||
Other frameworks | ||
OpenTelemetry | ||
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 |
---|---|---|
Tracing initialization | ||
Basic tracing | ||
Combine layers | ||
Configure a custom event formatter | ||
Events | ||
Spans | ||
Add tracing spans to functions | ||
Related crates |
Development Tools: FFI
Generate FFI Bindings
Development Tools: Procedural Macro-Helpers
Compile Macros
Recipe | Crates | Categories |
---|---|---|
Watt |
Tools
Recipe | Crates | Categories |
---|---|---|
cargo expand |
Write Proc Macros
Recipe | Crates | Categories |
---|---|---|
Parse Rust source code | ||
paste | ||
quote | ||
proc-macro2 | ||
darling |
Development Tools: Profiling
Assembly
Recipe | Crates | Categories |
---|---|---|
Inspect the generated assembly |
Benchmarking
Recipe | Crates | Categories |
---|---|---|
cargo flamegraph | ||
Criterion | ||
Divan | ||
Hyperfine |
Memory
Recipe | Crates | Categories |
---|---|---|
Profile heap memory |
Development Tools: Testing
Fuzzing
Recipe | Crates | Categories |
---|---|---|
afl |
Testing
Recipe | Crates | Categories |
---|---|---|
Test your code with cargo test | ||
Emit a custom message | ||
cargo nextest | ||
Approx | ||
Snapshot testing | ||
Code coverage |
Send Emails
Recipe | Crates | Categories |
---|---|---|
Send an email |
Embedded
Embassy
Recipe | Crates | Categories |
---|---|---|
Embassy |
Emulators
Emulators
Recipe | Crates | Categories |
---|---|---|
emulators |
Encoding
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 |
---|---|---|
JSON | ||
serde-json | ||
monostate | ||
serde-ignored |
Strings
Recipe | Crates | Categories |
---|---|---|
Percent-encode a string | ||
Encode a string as application/x-www-form-urlencoded | ||
Encode and decode hex | ||
Encode and decode base64 |
Typecasts
External FFI Bindings
External FFI Bindings
Recipe | Crates | Categories |
---|---|---|
ffi |
Filesystem
cwd
Recipe | Crates | Categories |
---|---|---|
Get the current working directory |
dir
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 Files
Recipe | Crates | Categories |
---|---|---|
Read lines of strings from a file | ||
Avoid writing and reading from a same file | ||
Access a file randomly using a memory map |
Tempfiles and Directories
Recipe | Crates | Categories |
---|---|---|
Create temporary files or temporary directories |
User Directories
Recipe | Crates | Categories |
---|---|---|
Get platform-specific locations for configuration, cache, and other data with dirs | ||
Get platform-specific locations for configuration, cache, and other data with directories |
Finance
Quant
Recipe | Crates | Categories |
---|---|---|
RustQuant |
Game Development
Game Development
Recipe | Crates | Categories |
---|---|---|
Game development |
Game Engines
Game Engines
Graphics
Webgpu
Recipe | Crates | Categories |
---|---|---|
wgpu |
GUI
2D Renderers
Clipboard
Recipe | Crates | Categories |
---|---|---|
arboard |
File Dialogs
Recipe | Crates | Categories |
---|---|---|
rfd |
GTK
Immediate Mode Gui
Recipe | Crates | Categories |
---|---|---|
eGUI |
Other GUI
Retained-mode GUI
Text Layout
Recipe | Crates | Categories |
---|---|---|
cosmic-text | ||
parley |
UI Layout
Web-based GUI
Window Creation
Hardware Support
Processor
Recipe | Crates | Categories |
---|---|---|
Get the number of logical cpu cores |
Internationalization
Internationalization
Recipe | Crates | Categories |
---|---|---|
internationalization |
Localization
Localization
Recipe | Crates | Categories |
---|---|---|
Localization |
Mathematics
Additional Numeric Types
Recipe | Crates | Categories |
---|---|---|
Abstracting over different number types | ||
Big integers | ||
Big decimal | ||
Sortable Floats |
Complex Numbers
Recipe | Crates | Categories |
---|---|---|
Creating complex numbers | ||
Adding complex numbers | ||
Mathematical functions on complex numbers |
Linear Algebra
Recipe | Crates | Categories |
---|---|---|
Vector Norm | ||
Adding matrices | ||
Multiplying matrices | ||
Multiply a scalar with a vector with a matrix | ||
Invert matrix | ||
Vector comparison | ||
(De)-Serialize a Matrix |
Statistics
Recipe | Crates | Categories |
---|---|---|
Computing standard deviation | ||
Measures of central tendency | ||
Standard deviation |
Trigonometry
Recipe | Crates | Categories |
---|---|---|
Calculating the side length of a triangle | ||
Verifying tan is equal to sin divided by cos | ||
Distance between two points on the Earth |
Memory Management
Global Static
Recipe | Crates | Categories |
---|---|---|
Declare lazily evaluated constant |
Lazy Initialization
Recipe | Crates | Categories |
---|---|---|
std | ||
once_cell | ||
lazy_static |
Multimedia Audio
Audio
Recipe | Crates | Categories |
---|---|---|
Audio |
Multimedia Encoding
Encoding
Recipe | Crates | Categories |
---|---|---|
Multimedia encoding |
Multimedia Images
Images
Recipe | Crates | Categories |
---|---|---|
Images |
Multimedia Video
Video
Recipe | Crates | Categories |
---|---|---|
Video |
Network Programming
Reverse Proxy
Server
Recipe | Crates | Categories |
---|---|---|
Listen on unused port TCP/IP | ||
Listen on unused port TCP/IP | ||
Perform asynchronous I/O operations on storage devices | ||
glommio |
No alloc
No alloc
Recipe | Crates | Categories |
---|---|---|
No alloc |
OS: FreeBSD APIs
FreeBSD
Recipe | Crates | Categories |
---|---|---|
FreeBSD |
OS: Linux APIs
Linux
Recipe | Crates | Categories |
---|---|---|
Linux |
OS: macOS APIs
macOS
Recipe | Crates | Categories |
---|---|---|
macOS |
OS: Unix APIs
Unix
OS: Windows APIs
Windows
Recipe | Crates | Categories |
---|---|---|
Integrate with Windows APIs | ||
winapi |
Parser Implementations
Parser Implementations
Recipe | Crates | Categories |
---|---|---|
JavaScript |
Parsing
Parsing
Recipe | Crates | Categories |
---|---|---|
Nom | ||
Pest | ||
Tree sitter |
Rendering: Data Formats
Data Formats
Recipe | Crates | Categories |
---|---|---|
Data formats |
Rendering Engine
Rendering Engines
Recipe | Crates | Categories |
---|---|---|
Render |
Rendering: Graphics APIs
Graphics APIs
Recipe | Crates | Categories |
---|---|---|
Graphics |
Rust Patterns
Design Patterns
Recipe | Crates | Categories |
---|---|---|
Abstract factory | ||
dyn-clone | ||
Lens |
Error Customization
Recipe | Crates | Categories |
---|---|---|
Anyhow | ||
thisError | ||
miette | ||
color-eyre |
Error Handling
Functional Programming
Recipe | Crates | Categories |
---|---|---|
Compose iterators with itertools |
Rust Idioms
Recipe | Crates | Categories |
---|---|---|
Rust idioms |
Science Geo
Geo
Recipe | Crates | Categories |
---|---|---|
Geoscience |
Science Neuroscience
Neuro
Recipe | Crates | Categories |
---|---|---|
Neuroscience |
Science Robotics
Robotics
Recipe | Crates | Categories |
---|---|---|
Robotics | robotics.rs |
Useful Robotics Tools And Libs
Recipe | Crates | Categories |
---|---|---|
Open CV | ||
zenoh | ||
Open Rust Robotics | ||
bonsai-bt |
Simulation
Simulation
Recipe | Crates | Categories |
---|---|---|
Simulation |
Template Engine
Tera
Recipe | Crates | Categories |
---|---|---|
Create HTML files from a template |
tinytemplate
Recipe | Crates | Categories |
---|---|---|
tinytemplate |
Text Editors
IDEs
Recipe | Crates | Categories |
---|---|---|
VS Code | VS Code | |
IntelliJ Rust | ||
Zed | ||
Helix Editor |
Text Processing
Regex
Recipe | Crates | Categories |
---|---|---|
Longer regex example |
String Concat
Recipe | Crates | Categories |
---|---|---|
Benchmark |
String Parsing
Recipe | Crates | Categories |
---|---|---|
Collect Unicode Graphemes | ||
Implement the FromStr trait for a custom struct |
Value Formatting
Value Formatting
Recipe | Crates | Categories |
---|---|---|
Value formatting |
Virtualization
Virtualization
Recipe | Crates | Categories |
---|---|---|
Virtualization |
Visualization
Visualization
Recipe | Crates | Categories |
---|---|---|
Visualization |
Wasm
Others
Yew
Recipe | Crates | Categories |
---|---|---|
Yew |
Web Programming: HTTP Client
APIs
Recipe | Crates | Categories |
---|---|---|
Query the GitHub API | ||
Check if an API resource exists | ||
Create and delete Gist with 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 | ||
Set custom headers and URL parameters for a REST request | ||
Async |
Web Pr: HTTP Server
actix
Recipe | Crates | Categories |
---|---|---|
Actix |
axum
Recipe | Crates | Categories |
---|---|---|
Axum |
Batteries-included frameworks
Recipe | Crates | Categories |
---|---|---|
Loco | ||
Rust on Nails |
CORS
Recipe | Crates | Categories |
---|---|---|
CORS |
GraphQL
Recipe | Crates | Categories |
---|---|---|
async-graphql |
gRPC
Recipe | Crates | Categories |
---|---|---|
tonic |
hyper
Recipe | Crates | Categories |
---|---|---|
hyper |
Middleware
Recipe | Crates | Categories |
---|---|---|
Tower | ||
Tower HTTP | ||
Alternatives |
Other Frameworks
Static Website Generators
Recipe | Crates | Categories |
---|---|---|
Static website generator - Zola | ||
Zola themes |
Web-Programming Websocket
Websocket
Recipe | Crates | Categories |
---|---|---|
Low-level | ||
General Purpose |
Contributing
API Documentation
Section |
---|
Generate the docs.rs Documentation |
Using a Dev Container feature |
Other methods to preview the documentation HTML |
Development Editing
Section |
---|
Book editing |
Dev Container Docker
Dev Environment Setup
Optional Preprocessors
Section |
---|
Optional pre-processors |
Publication
Section |
---|
Publication |
Repo Structure
Section |
---|
Repo structure |
Topics of Interest
Section |
---|
Topics of interest |
Language
Attributes
Section |
---|
Attributes |
Lint attributes |
Automatic trait derivation |
Must Use |
Deprecated |
Conditional Compilation |
Closures
Control Flow
Section |
---|
Control flow |
If else |
Loop |
While |
For |
Enums
Section |
---|
Enums |
Functions
Generics
Section |
---|
Generics |
Generic Structs |
Iterators
Section |
---|
Iterators |
Lifetimes
Macros
Section |
---|
Macros |
Main
Section |
---|
Main function |
Async Main Function |
Match
Modules
Section |
---|
Modules |
use keyword |
Ownership Borrowing
Rust Install
Section |
---|
Rust installation |
Simple Data Types
Section |
---|
Simple data types |
Overflow handling |
Slices
Section |
---|
Slices |
Structs
Section |
---|
Structs |
Traits
Trait Objects
Section |
---|
trait-objects |
Variables and Constants
Links
Blogs, Podcasts, Meetups
Section |
---|
Meetups |
Newsletters |
Podcasts & blogs |
Books
Section |
---|
Books |
Companies
Example Code
Recipe |
---|
example-code |
Learning
Section |
---|
Rust Learning |
Links
Section |
---|
Rust Links |
Rust Cheatsheets
Architecture
Architecture
Recipe | Crates | Categories |
---|---|---|
Architecture |
Cloud
AWS
Recipe | Crates | Categories |
---|---|---|
Develop and deploy applications with the AWS SDK for Rust | AWS Rust SDK | |
Implement Lambda functions in Rust |
Rust-native Cloud Development
Recipe | Crates | Categories |
---|---|---|
Shuttle Cloud Platform |
Serverless
Recipe | Crates | Categories |
---|---|---|
Implement serverless computing with Rust | ||
Use the dapr distributed runtime with Rust |
Containers
Containers
Recipe | Crates | Categories |
---|---|---|
Docker | ||
Docker Compose |
Cross Platform
Crux
Recipe | Crates | Categories |
---|---|---|
Develop across platforms with Crux |
Data Processing
CSV
Recipe | Crates | Categories |
---|---|---|
Read and write CSV files | ||
Manipulate CSV files from the command line |
Dataframes
Recipe | Crates | Categories |
---|---|---|
Manipulate data in a tabular format |
Data Engineering
Recipe | Crates | Categories |
---|---|---|
Develop data analytics applications that process columnar data with Arrow | ||
Query in-memory data with datafusion | ||
Databend | ||
Arrow | ||
Datafusion |
Data Visualization
Recipe | Crates | Categories |
---|---|---|
Plot and graph data |
Devops
CD/CI
Recipe | Crates | Categories |
---|---|---|
CD / CI |
Dependency Management
Recipe | Crates | Categories |
---|---|---|
deps.rs | ||
Rust Digger |
DevOps
Recipe | Crates | Categories |
---|---|---|
Daytona |
Github Actions
Recipe | Crates | Categories |
---|---|---|
Installation of development tools | ||
Compilation caching |
Git Hooks
Recipe | Crates | Categories |
---|---|---|
Check your code before committing it | ||
pre-commit |
Release Automation
Recipe | Crates | Categories |
---|---|---|
cargo release | ||
release-plz |
GPU
GPU
Recipe | Crates | Categories |
---|---|---|
rust-gpu |
Scripting
rhai
Recipe | Crates | Categories |
---|---|---|
Embed Rust scripting in your application |
Written in Rust
Development Tools
Recipe | Crates | Categories |
---|---|---|
Compilers | ||
swc | ||
Code editors | ||
lapce | ||
zed | ||
Build tools |
Others
Recipe | Crates | Categories |
---|---|---|
File managers | ||
Remote desktops | ||
Email applications | ||
News | ||
Payments | ||
Social media |
Python Tools
Recipe | Crates | Categories |
---|---|---|
rustpython | ||
pyOxidizer | ||
Ruff | ||
uv |
Standard Library
Asref
Recipe | Crates |
---|---|
AsRef |
Cow
Recipe | Crates |
---|---|
Convert Cow to &str | |
Convert Cow to String |
Derive
Recipe | Crates |
---|---|
Automatically implement common traits using the derive attribute | |
Automatically implement additional traits using derive_more |
Hashmaps
Recipe | Crates |
---|---|
Hashmaps |
Option
Recipe | Crates |
---|---|
Option | |
Adapters for working with references | |
Extracting the value contained in Option | |
Combinators |
Result
Recipe | Crates |
---|---|
Result |
Smart Pointers
Strings
Recipe | Crates |
---|---|
String type | |
Placeholders | |
String concatenation |
Vectors
Recipe | Crates |
---|---|
Vec |
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
Section |
---|
Topics of interest |
- Async
- CD / CI
- Macros
- AWS and other Cloud services
- Advanced data structures
- Basic and advanced TCP/IP networking
- More database examples, including object databases, graph databases, BonsaiDB
- Zip files and other archives
- Authentication / authorization: OAuth2, LDAP/AD, DNS lookups
- FLTK
- GPU processing, CUDA
- Machine learning, Tensorflow
- Raft Consensus library
- Network file systems
- Statistics, math
- Crypto, SSL, SSH, other public key encryption, X.509, RusTLS
- Sound, graphics
- Games
- Search engines
- Buffer pools, garbage collection, or other reference-counted examples
- IPv6 address processing
- Cloud stuff: LB, status reporting (Vigil), routing, orchestration, containers
- Version control: libgit2: clone, change branches, create commits, push, pull
- High-performance computing: OpenMP, etc.
- Social media APIs
- Personal file sharing: OwnCloud, etc.
Repo structure
Section |
---|
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
deps/tests
anddeps/examples
. These are mostly single, short.rs
files. Thedeps/Cargo.toml
list all dependencies used by the embedded examples. Usecargo add <crate> -F <feature>
while in thedeps
folder to add more as required.deps/build.rs
creates the Skpetic tests that validate all embedded examples. - Additional examples that are too long or complex to be inserted in the book itself will be added under
xmpl
. tools
contains utilities that e.g. generate the sitemap file and organize links.
- Examples that are embedded in the book are found in
- The Dev Container and Docker (Compose) configuration files are found in
.devcontainer
.
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.
TODO windows install
winget install openssl
need Python
Book Editing and Example Code Development
Section |
---|
Book editing |
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
deps/tests
⮳.- 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
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
Optional pre-processors
Section |
---|
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
Section |
---|
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
Section |
---|
Publication |
The publish
folder contains a placeholder crate, so that the book could be located when searching on crates.io
.
- 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 publish; cargo build; cargo clippy; cargo run; cargo doc; cargo package
- Review the packaging output in
/cargo-target-rust_howto/target/package
. - When ready,
cargo publish --dry-run; cargo publish
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
Section |
---|
Rust installation |
Key Steps
- 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
Section |
---|
Main function |
Async Main Function |
use std::fs::File; use std::io::Read; use anyhow::anyhow; use anyhow::Result; 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
Section |
---|
Simple data types |
Overflow handling |
- 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;
Overflow handling
- 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.
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; let apples = 5; // immutable variable let mut guess = String::new(); // mutable variable }
Shadowing
#![allow(unused)] fn main() { let x = 5; // x is immutable let x = x + 1; // redefines x println!("{x}"); let x = "example"; // the type can change println!("{x}"); }
Destructuring
#![allow(unused)] fn main() { // destructuring tuples let (_x, _y, _z) = (1, 2, 3); // destructuring structs struct Point { x: i32, y: i32, } let p = Point { x: 0, y: 7 }; let Point { x: _a, y: _b } = p; // a = 0, b = 7 let Point { x, y } = p; // simpler let _ = (x, y); }
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.
#![allow(unused)] fn main() { let s1 = String::from("hello"); // On the heap let _s2 = s1; // s1 was MOVED into s2 - NOT a shallow copy - Rust // invalidates s1 ERROR println!(" // {}, world!", s1); }
When the owner goes out of scope, the value will be dropped.
#![allow(unused)] fn main() { { let _s = String::from("hello"); } // variable 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 s2 = String::from("hello"); let _s3 = s2.clone(); // `clone` deeply copies the heap data of the `String`, // not just the stack data }
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.
#![allow(unused)] 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); // pass an immutable reference to s1 fn calculate_length(s: &str) -> usize { s.len() } // Here, s goes out of scope. But because it does not have // ownership of what it refers to, s1 is not dropped. }
Mutable reference
fn change(some_string: &mut String) { // note the `&mut` some_string.push_str(", world"); } 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
Section |
---|
Slices |
Slices
#![allow(unused)] 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!("got t"); } // 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. t must be // some kind of 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
Section |
---|
Control flow |
If else |
Loop |
While |
For |
If else
#![allow(unused)] fn main() { let number = 3; let result = if number < 5 { // condition must return a bool; `if` is an expression println!("condition was true"); 5 } else { println!("condition was false"); 6 }; println!("{}", result); }
Also else if <cond> { ... }
Loop
#![allow(unused)] 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
#![allow(unused)] fn main() { let mut number = 5; while number != 0 { println!("{number}!"); number -= 1; } }
For
#![allow(unused)] 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
Section |
---|
Structs |
struct User { active: bool, username: String, email: String, sign_in_count: u64, } fn main() { // create an instance let _user1 = User { active: true, username: String::from("someusername123"), email: String::from("someone@example.com"), sign_in_count: 1, }; }
Struct fields follow the general rule of everything being private by default unless annotated with pub
⮳.
struct User { active: bool, username: String, email: String, } fn build_user(email: String, username: String) -> User { User { active: true, username, // instead of username: username - field init shorthand email, // same } } fn main() { let user1 = build_user("<user@example.com>".into(), "user".to_string()); // struct update let _user2 = User { email: String::from("another@example.com"), ..user1 /* the remaining fields not explicitly set should have the * same value as the fields in the given instance. */ }; }
// Tuple struct struct Color(i32, i32, i32); // Unit-like struct struct AlwaysEqual; // <-- no fields fn main() { let _black = Color(0, 0, 0); let _s = AlwaysEqual; }
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
Section |
---|
Enums |
enum Message { Quit, Move { x: i32, y: i32 }, // struct-like Write(String), // tuple-like ChangeColor(i32, i32, i32), } // Define methods on enums. impl Message { fn call(&self) { // method body would be defined here } } fn main() { let _home = Message::ChangeColor(127, 0, 0); // <-- note the :: }
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; impl<T> Test<T> for SomeStruct { // note the <> in two places fn test(_t: T) { println!("test"); } } fn main() { SomeStruct::test(1); SomeStruct::test(true); }
Associated types
trait Iterator { type Item; // <-- associated type // in Impl, use e.g. `Iterator<Item = u32>` fn next(&mut self) -> Option<Self::Item>; } // Generic type with default trait Add<Rhs = Self> { type Output; // <-- associated type fn add(self, rhs: Rhs) -> Self::Output; } fn main() {}
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
Section |
---|
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 &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
is made up of an object-safe-reference⮳ 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⮳).
#![allow(unused)] fn main() { dyn Trait dyn Trait + Send dyn Trait + Send + Sync dyn Trait + 'static }
See also
Attributes
Section |
---|
Attributes |
Lint attributes |
Automatic trait derivation |
Must Use |
Deprecated |
Conditional Compilation |
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)] use std::thread; pub struct S; #[must_use] fn required() -> u32 { 42 } fn dead_code() {} fn main() { let x = 1; let mut m = 2; }
For production-ready code, replace the above by the following, for example.
//! Crate documentation #![warn( unused, missing_debug_implementations, missing_copy_implementations, missing_docs, rust_2018_idioms )] #![deny(unreachable_pub)] // error if violation #![forbid(unsafe_code)] // same as `deny` +forbids changing the lint level afterwards /// This is the required documentation for S #[derive(Debug, Copy, Clone)] struct S; /// Here is the main test function! fn main() { let _ = S; }
You also apply these attributes to specific functions:
// Disables the `dead_code` lint #[allow(dead_code)] fn unused_function() {} fn main() {}
List of lint checks: rustc -W help
. rustc
⮳ also recognizes the tool lints for "clippy" and "rustdoc" e.g. #![warn(clippy::pedantic)]
Automatic trait derivation
See Automatic derivation.
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)); }
Deprecated
#![allow(deprecated)] // Removes the warning. #[deprecated(since = "5.2.0", note = "Use bar instead")] pub fn foo() {} fn main() { foo(); }
Conditional Compilation
// 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
Section |
---|
Generics |
Generic Structs |
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<'a> ImportantExcerpt<'a> { fn level(&self) -> i32 { 3 } } fn main() { let ie = ImportantExcerpt { part: "a part" }; println!("{}", ie.level()); }
Modules
Section |
---|
Modules |
use keyword |
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.
// 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 std::collections::*; // With the following, `File` without prefix is available in the scope use std::fs::File; // The following is equivalent to `use std::io; use std::io::Write;` use std::io::{self, Write}; // Combine multiple `use` lines together with { } use std::{cmp::Ordering, fmt}; mod utils { pub fn insert_use() {} } // Absolute path - for code from the current crate, it starts with the // literal `crate`. use crate::modules::utils::insert_use; mod a { pub mod b {} } // A relative path starts from the current module and uses `self`, // `super`, or an identifier in the current module. use self::a::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; } use std::fmt::Result; use std::io::Result as IoResult; // Alias mod front_of_house { pub mod hosting {} } // Reexporting - `pub use` re-exports the `hosting` module from the // root module, thus external code can use the path // `<crate>::hosting::add_to_waitlist()` instead of // `crate::front_of_house::hosting::add_to_waitlist()`. pub use front_of_house::hosting; fn main() {}
Idiomatic - bringing the function’s parent module into scope, not the function itself:
use front_of_house::hosting; mod front_of_house { pub mod hosting { pub fn add_to_waitlist() {} } } fn eat_at_restaurant() { 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.
use std::collections::HashMap; fn main() { let mut _map: HashMap<u32, String> = HashMap::new(); }
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
#![allow(unused)] 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
#![allow(unused)] fn main() { 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
Section |
---|
Iterators |
#![allow(unused)] 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
Section |
---|
Macros |
The Little Book of Rust Macros⮳
fn main() { // Used as an expression. let _x = vec![1, 2, 3]; // Used as a statement. println!("Hello!"); // Used in a pattern. macro_rules! pat { ($i:ident) => { Some($i) }; } if let pat!(x) = Some(1) { assert_eq!(x, 1); } } // Used in a type. macro_rules! Tuple { { $A:ty, $B:ty } => { ($A, $B) }; } type _N2 = Tuple!(i32, i32); // Used as an item. // thread_local!(static FOO: RefCell<u32> = RefCell::new(1)); // Used as an associated item. macro_rules! const_maker { ($t:ty, $v:tt) => { const CONST: $t = $v; }; } #[allow(dead_code)] trait T { 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 | |
Adapters for working with references | |
Extracting the value contained in Option | |
Combinators |
Result
Recipe | Crates |
---|---|
Result |
Vectors
Recipe | Crates |
---|---|
Vec |
HashMap
Recipe | Crates |
---|---|
Hashmaps |
Strings
Recipe | Crates |
---|---|
String type | |
Placeholders | |
String concatenation |
Copy-on-write
Recipe | Crates |
---|---|
Convert Cow to &str | |
Convert Cow to String |
Smart Pointers
Automatic Trait Derivation
Recipe | Crates |
---|---|
Automatically implement common traits using the derive attribute | |
Automatically implement additional traits using derive_more |
Asref
Recipe | Crates |
---|---|
AsRef |
Option
Recipe | Crates |
---|---|
Option | |
Adapters for working with references | |
Extracting the value contained in Option | |
Combinators |
Option
Rust has no null
. Instead, use std::option::Option
⮳:
#![allow(unused)] fn main() { 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.
#![allow(unused)] 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 base..."); // 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() { bake_cake(Some("rainbow nonpareils")); }
Adapters for 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>
Extracting 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
Combinators
use std::fs; // `and_then` applies a function to the wrapped value if it's Some. fn read_file(filename: &str) -> Option<String> { fs::read_to_string(filename) .ok() // Convert `Result` to `Option` .and_then(|contents| Some(contents.trim().to_string())) // `and_then` chains operations on `Some` } fn main() { let contents = read_file("poem.txt"); // Using `match` to process the returned Option. match contents { Some(poem) => println!("{}", poem), None => println!("Error reading file"), } }
Result
Recipe | Crates |
---|---|
Result |
Result
See also
Vectors
Recipe | Crates |
---|---|
Vec |
Vec
Vectors can only store values that are the same type.
#![allow(unused)] 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 type | |
Placeholders | |
String concatenation |
String type
#![allow(unused)] 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
#![allow(unused)] 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).
String concatenation
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); }
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
⮳:
#![allow(unused)] 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
⮳:
#![allow(unused)] 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:
#![allow(unused)] 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:
#![allow(unused)] 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
⮳:
#![allow(unused)] fn main() { let example = std::borrow::Cow::from("example"); let s = example.to_string(); println!("{}", s); }
Use std::borrow::Cow::into_owned
⮳:
#![allow(unused)] 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
⮳:
#![allow(unused)] 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
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.
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 references. Implementing the std::ops::Deref
⮳ trait allows you to customize the behavior of the dereference operator *
.
Use when
- you have a 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.
Rc
The Rc<T>
type keeps track of the number of references to data on the heap so that data can have multiple owners.
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.
AsRef and &T
Recipe | Crates |
---|---|
AsRef |
When and why to use AsRef
Automatic trait derivation
Recipe | Crates |
---|---|
Automatically implement common traits using the derive attribute | |
Automatically implement additional traits using derive_more |
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
Sorting
Recipe | Crates | Categories |
---|---|---|
Sort a Vector of Integers | ||
Sort a Vector of Floats | ||
Sort a Vector of Structs |
Generate Random Values
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(); let n1: u8 = rng.gen(); let n2: u16 = rng.gen(); println!("Random u8: {}", n1); println!("Random u16: {}", n2); println!("Random u32: {}", rng.gen::<u32>()); println!("Random i32: {}", rng.gen::<i32>()); println!("Random float: {}", rng.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::distributions::Distribution; use rand::distributions::Standard; use rand::Rng; #[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.gen(); Point { x: rand_x, y: rand_y, } } } fn main() { let mut rng = rand::thread_rng(); let rand_tuple = rng.gen::<(i32, bool, f64)>(); let rand_point: Point = rng.gen(); println!("Random tuple: {:?}", rand_tuple); println!("Random Point: {:?}", rand_point); }
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::distributions::Alphanumeric; use rand::thread_rng; use rand::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); }
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]); }
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]); }
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), ] ); // 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), ] ); }
See also
glidesort⮳ is a Rust implementation of Glidesort, a stable adaptive quicksort/mergesort hybrid sorting algorithm.
Architecture + Rust
Recipe | Crates | Categories |
---|---|---|
Architecture |
Links
Building a SaaS with Rust and Next.js⮳
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 { // ... SomeStruct } async fn second_task_1(_s: &SomeStruct) { // ... } // `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 {} // 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 = // ["full"] }` Technically, the #[tokio::main] attribute is a macro // 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; } } async fn do_async_op() -> bool { true } async fn log_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
.
// TODO #![allow(dead_code)] trait Container { fn items(&self) -> impl Iterator<Item = u8>; // <-- return Impl in a trait } 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) {} async fn hide_for_now(&self) {} } async fn remind_user_to_join_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::sleep;
use tokio::time::Duration;
use tokio_graceful_shutdown::SubsystemBuilder;
use tokio_graceful_shutdown::SubsystemHandle;
use tokio_graceful_shutdown::Toplevel;
// use tracing::Level;
async fn countdown() {
for i in (1..=5).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()
// .with_max_level(Level::TRACE)
.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(); }
Multiple Producer, 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; }
See also
Fast sync and async channel:
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::StreamExt; use futures::Stream; 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 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...
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 {
if let Err(e) = fut.await {
println!("Error: {}", e);
if let Some(source) = e.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 { 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 }); assert_eq!(future_of_7.await, 7); // Conditional `Either` future let x = 6; let future = if x > 10 { async { true }.left_future() } else { async { false }.right_future() }; assert!(!(future.await)); // Flatten nested futures let nested_future = async { async { 1 } }; let future = nested_future.flatten(); assert_eq!(future.await, 1); Ok(()) }
See also
futures = Utility functions for working with Futures and Streams
Mixing Async and Blocking Code
Calling 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⮳.
Tokio 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(); }
Using 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.
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 }
Using 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 |
---|---|---|
Basic Authentication |
Basic Authentication
Recipe | Crates | Categories |
---|---|---|
Basic 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(()) }
Cloud
This section covers the use of the Rust language in cloud computing and serverless computing.
AWS
Recipe | Crates | Categories |
---|---|---|
Develop and deploy applications with the AWS SDK for Rust | AWS Rust SDK | |
Implement Lambda functions in Rust |
Serverless
Recipe | Crates | Categories |
---|---|---|
Implement serverless computing with Rust | ||
Use the dapr distributed runtime with Rust |
Rust-native Clouds
Recipe | Crates | Categories |
---|---|---|
Shuttle Cloud Platform |
Implement Rust applications on AWS
Recipe | Crates | Categories |
---|---|---|
Develop and deploy applications with the AWS SDK for Rust | AWS Rust SDK | |
Implement Lambda functions in Rust |
At AWS, Rust has quickly become critical to building infrastructure at scale. Firecracker
is an open source virtualization technology that powers AWS Lambda and other serverless offerings.
Develop and deploy applications with the AWS SDK for Rust
Call AWS services using idiomatic Rust APIs.
Implement Lambda functions in Rust
Use the AWS SDK from within a Lambda function
Create Lambda functions with Rust⮳
aws-lambda-rust-runtime-examples⮳
Serverless Rust
Recipe | Crates | Categories |
---|---|---|
Implement serverless computing with Rust | ||
Use the dapr distributed runtime with Rust |
Use Rust for serverless computing
Use the dapr
distributed runtime
Dapr⮳ is a portable, event-driven, serverless runtime for building distributed applications across cloud and edge.
Rust-native cloud development
Recipe | Crates | Categories |
---|---|---|
Shuttle Cloud Platform |
Cloud platforms that offer first-class support for Rust.
Shuttle
docs.shuttle.rs shuttle-examples
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 |
Recipe | Crates | Categories |
---|---|---|
ANSI Terminal | ||
Printing colored text to the Terminal | ||
Bold text in Terminal | ||
Bold and colored text in terminal | ||
Manipulate the cursor, style the output, handle input events |
Recipe | Crates | Categories |
---|---|---|
Build complex TUI | {{hi:Command-line interface}} |
Recipe | Crates | Categories |
---|---|---|
Ask for confirmation, selection, text input | ||
Display progress bars and spinners | {{hi:indicatif}} |
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 |
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::value_parser; use clap::Arg; use clap::Command; 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](https://docs.rs/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
pico-args
Fast compile times, fast runtime, more lax about correctness. API is more ergonomic
ANSI Terminal
Recipe | Crates | Categories |
---|---|---|
ANSI Terminal | ||
Printing colored text to the Terminal | ||
Bold text in Terminal | ||
Bold and colored text in terminal | ||
Manipulate the cursor, style the output, handle input events |
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.
Printing 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") ); }
Bold text in 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") ); }
Bold and colored text in 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
- anstyle
- anstream
- nu-ansi-term
Others
Terminal User Interfaces
Recipe | Crates | Categories |
---|---|---|
Build complex TUI | {{hi:Command-line interface}} |
Build complex TUI
ratatui⮳ is a lightweight, high-level library that provides a set of widgets, layouts, and utilities to build complex Rust TUIs.
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 | {{hi:indicatif}} |
Ask for confirmation, selection, text input
inquire
provides several different prompts in order to interactively ask the user for information via the CLI.
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.
Command-line utilities written in Rust
Recipe | Crates | Categories |
---|---|---|
Networking tools: gping |
Recipe | Crates | Categories |
---|---|---|
Shells and related utilities |
See also
My terminal became more Rusty Community⮳
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 |
---|---|---|
Networking tools: gping |
gping
Ping, but with a graph gping
apt install gping
Shells and related utilities
Recipe | Crates | Categories |
---|---|---|
Shells and related utilities |
starship
is a fast, highly customizable prompt for any shell.
Compression
Algorithms for making data smaller.
Recipe | Crates | Categories |
---|---|---|
Decompress a tarball | ||
Compress a directory into a tarball | ||
Decompress a tarball while removing a prefix from the paths |
Working with Tarballs
Recipe | Crates | Categories |
---|---|---|
Decompress a tarball | ||
Compress a directory into a tarball | ||
Decompress a tarball while removing a prefix from the paths |
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(".")?; 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::create_dir; use std::fs::exists; use std::fs::File; use flate2::write::GzEncoder; use flate2::Compression; pub fn main() -> Result<(), std::io::Error> { // Create a temporary folder if !exists("temp")? { 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")?; 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(()) }
flate2
uses a pure-Rust implementation by default. Use feature flags to opt in to system zlib.
Concurrency
This section covers concurrent and parallel programming.
Recipe | Crates | Categories |
---|---|---|
Spawn, join | ||
Scoped threads | ||
Rayon - parallel processing | ||
Parallel iteration | ||
Parallel sorting | ||
Custom parallel tasks |
Recipe | Crates | Categories |
---|---|---|
Multiple producers, single consumer | ||
Crossbeam_channel |
Parallelism
-
True simultaneous execution of multiple tasks on multiple cores or processors.
-
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.
Here are the topics we’ll cover:
See Also
Send, Sync traits
Send
Sync
Multithreading
Recipe | Crates | Categories |
---|---|---|
Spawn, join | ||
Scoped threads | ||
Rayon - parallel processing | ||
Parallel iteration | ||
Parallel sorting | ||
Custom parallel tasks |
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"); }
When the main thread of a Rust program completes, all spawned threads are shut down, whether or not they have finished running.
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); } }
Rayon - parallel processing
Parallel iteration
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[..])); }
Parallel sorting
use rayon::prelude::*; fn main() { let mut v = [-5, 4, 1, -3, 2]; v.par_sort(); println!("{:#?}", v); }
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 }
See also
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)); } 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); } }
Maintain 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::anyhow; use anyhow::Result; 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(()) }
Calculate SHA256 sum 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 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::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); } img.save("temp/output.png")?; Ok(()) }
Channels
crossbeam-channel
The absolute fastest channel implementation available. Implements Go-like 'select' feature.
flume
Smaller and simpler than crossbeam-channel and almost as fast
tokio
Tokio's sync module provides channels for using in async code
postage
Channels that integrate nicely with async code, with different options than Tokio
Parallel Tasks
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)); }
Search items using 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)); }
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::distributions::Alphanumeric; use rand::thread_rng; use rand::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 jpg 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::glob_with; use glob::MatchOptions; use image::imageops::FilterType; use rayon::prelude::*; // TODO address the need for test jpg 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(()); // or use anyhow::bail!() } 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
Recipe | Crates | Categories |
---|---|---|
Multiple producers, single consumer | ||
Crossbeam_channel |
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
Recipe | Crates | Categories |
---|---|---|
Multiple producers, single consumer | ||
Crossbeam_channel |
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.
use std::thread; use crossbeam_channel::unbounded; use crossbeam_channel::RecvError; use crossbeam_channel::TryRecvError; 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, } } }
See also
Shared-State Concurrency
Recipe | Crates | Categories |
---|---|---|
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.
Mutex
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
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; static START: Once = Once::new(); fn main() { // run a one-time initialization START.call_once(|| { // run initialization here }); }
use parking_lot::RwLock; fn main() { let lock = RwLock::new(5); // many reader locks can be held at once { let r1 = lock.read(); let r2 = lock.read(); assert_eq!(*r1, 5); assert_eq!(*r2, 5); } // read locks are dropped at this point // only one write lock may be held, however { let mut w = lock.write(); *w += 1; assert_eq!(*w, 6); } // 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); }
arc-swap
The ArcSwap type is a container for an Arc that can be changed atomically. Semantically, it is similar to something like Atomic<Arc
Concurrent Data Structures
Recipe | Crates | Categories |
---|---|---|
Dashmap | ||
Bounded Multi-producer Multi-consumer Queue | ||
flurry |
Dashmap
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 => {
if let Some(value) = map_clone.get("key1") {
println!("Thread {} read key1: {}", thread_id, *value);
} else {
println!("Thread {} couldn't find key1", thread_id);
}
}
3 => {
if let Some(mut value) = map_clone.get_mut("key2") {
*value += 10;
println!(
"Thread {} incremented key2 value to {}",
thread_id, *value
);
} else {
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
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')); }
flurry
Particularly good for read-heavy workloads.
conc map bench comparative benchmarks of concurrent HashMaps.
Configuration
Facilitate configuration management for applications.
Configuration Management
Recipe | Crates | Categories |
---|---|---|
Configuration Management with Config | ||
Configuration Management with Confy |
Environment Variables
Recipe | Crates | Categories |
---|---|---|
Read environment variables from a .env file | ||
Retrieve a single environment variable | ||
Deserialize environment variables into type-safe structs |
Environment variables
Recipe | Crates | Categories |
---|---|---|
Read environment variables from a .env file | ||
Retrieve a single environment variable | ||
Deserialize environment variables into type-safe structs |
Dotenvy
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. dotenvy::dotenv()?; 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", 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
Recipe | Crates | Categories |
---|---|---|
Configuration Management with Config | ||
Configuration Management with Confy |
Config
config
⮳ is a layered configuration system for Rust applications. It reads from JSON, TOML, YAML, INI, RON, JSON5 files.
Confy
use serde::Deserialize; use serde::Serialize; #[derive(Serialize, Deserialize)] 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)?; Ok(()) }
Rust + containers
Recipe | Crates | Categories |
---|---|---|
Docker | ||
Docker Compose |
Docker
References
- Docker's getting started⮳ docs.
- Docker's Rust guide⮳
Docker Compose
An easy way to get started is to run docker init
in a new folder, select Rust
as the project type, then edit the provided compose.yaml
and Dockerfile
.
cargo init .
docker init
- Build your image:
docker build -t myapp .
. - If your cloud uses a different CPU architecture than your development machine (e.g., you are on a Mac M1 and your cloud provider is amd64), you'll want to build the image for that platform, e.g.:
docker build --platform=linux/amd64 -t myapp .
. - Start your application by running:
docker compose up --build
Cross-platform Development
Crux
Recipe | Crates | Categories |
---|---|---|
Develop across platforms with Crux |
Cross-platform development with Crux
Recipe | Crates | Categories |
---|---|---|
Develop across platforms with Crux |
Develop across platforms with Crux
crux
⮳ is an experimental approach to building cross-platform applications.
It splits the application into two distinct parts, a Core built in Rust, which drives as much of the business logic as possible, and a Shell, built in the platform native language (Swift, Kotlin, TypeScript), which provides all interfaces with the external world, including the human user, and acts as a platform on which the core runs.
The architecture is event-driven, based on event sourcing. The Core holds the majority of state, which is updated in response to events happening in the Shell. The interface between the Core and the Shell is message-based.
The user interface layer is built natively, with modern declarative UI frameworks such as Swift UI, Jetpack Compose and React/Vue or a WASM based framework on the web.
Cryptography
Securing data.
Encryption
Recipe | Crates | Categories |
---|---|---|
Salt and hash a password with PBKDF2 |
Hashing
Password Hashing
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::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<()> { 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 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> { 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); let message = "Legitimate and important message."; let signature = hmac::sign(&key, message.as_bytes()); hmac::verify(&key, message.as_bytes(), signature.as_ref())?; Ok(()) }
review below from blessed.rs
General Purpose Hashing
For more algorithms, see Rust Crypto Hashes.
AEAD Encryption
For more algorithms, see Rust Crypto AEADs.
RSA
Digital Signatures
For more algorithms, see Rust Crypto Signatures.
ed25519 Use in conjunction with the ed25519-dalek crate.
Certificate Formats
For more formats, see Rust Crypto Formats.
TLS / SSL
rustls A portable pure-rust high-level implementation of TLS. Implements TLS 1.2 and higher.
native-tls Delegates to the system TLS implementations on windows and macOS, and uses OpenSSL on linux.
Utilities
Password Hashing
argon2
Pure Rust implementation of the Argon2 password hashing function with support for the Argon2d, Argon2i, and Argon2id algorithmic variants
scrypt
The scrypt key derivation function is designed to be far more secure against hardware brute-force attacks than alternative functions such as PBKDF2 or bcrypt.
bcrypt
Hash and verify passwords.
For more algorithms, see Rust Crypto Password Hashes
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, ); 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_succeed.is_ok()); assert!(should_fail.is_err()); Ok(()) }
Data and ETL
CSV
Recipe | Crates | Categories |
---|---|---|
Read and write CSV files | ||
Manipulate CSV files from the command line |
Dataframes
Recipe | Crates | Categories |
---|---|---|
Manipulate data in a tabular format |
Data Engineering
Recipe | Crates | Categories |
---|---|---|
Develop data analytics applications that process columnar data with Arrow | ||
Query in-memory data with datafusion | ||
Databend | ||
Arrow | ||
Datafusion |
Data Visualization
Recipe | Crates | Categories |
---|---|---|
Plot and graph data |
CSV files
Recipe | Crates | Categories |
---|---|---|
Read and write CSV files | ||
Manipulate CSV files from the command line |
Read and write CSV files
Manipulate CSV files from the command line
A fast CSV command line toolkit written in Rust. xsv⮳
xsv
is a command line program for indexing, slicing, analyzing, splitting and joining CSV files.
Dataframes
Recipe | Crates | Categories |
---|---|---|
Manipulate data in a tabular format |
Manipulate data in a tabular format
Similar to the Pandas library in Python but in pure Rust. Uses the Apache Arrow Columnar Format as the memory model.
Data Visualization
Recipe | Crates | Categories |
---|---|---|
Plot and graph data |
Plot and graph data
Plotly.rs
is a plotting library powered by Plotly.js⮳. The aim is to bring over to Rust all the functionality that Python
users have come to rely on; with the added benefit of type safety and speed.
Data Engineering
Recipe | Crates | Categories |
---|---|---|
Develop data analytics applications that process columnar data with Arrow | ||
Query in-memory data with datafusion | ||
Databend | ||
Arrow | ||
Datafusion |
Develop data analytics applications that process columnar data with Arrow
arrow
is the official Rust implementation of Apache Arrow
Query in-memory data with datafusion
is the Apache Arrow DataFusion
SQL Query Engine.
Apache DataFusion is an in-memory query engine that uses Apache Arrow as the memory model
DataFusion offers SQL and Dataframe APIs, excellent performance, built-in support for CSV, Parquet, JSON, and Avro, plus extensive customization. DataFusion is great for building projects such as domain specific query engines, new database platforms and data pipelines, query languages and more.
Databend
𝗗𝗮𝘁𝗮, 𝗔𝗻𝗮𝗹𝘆𝘁𝗶𝗰𝘀 & 𝗔𝗜. Modern alternative to Snowflake. Cost-effective and simple for massive-scale analytics. datafuselabs databend⮳
See also
Rust Data Engineering course By Alfredo Deza et al., O'Reilly
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 |
---|---|---|
Insertion-ordered map | ||
Multimap | ||
Slotmap |
Stack-allocated arrays
UUIDs
Recipe | Crates | Categories |
---|---|---|
Generate and parse UUIDs |
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
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); 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)" ); }
Flagset
FlagSet is a new, ergonomic approach to handling flags that combines the best of existing crates like bitflags
and enumflags
without their downsides.
#![allow(unused)] fn main() { use std::os::raw::c_int; use flagset::flags; flags! { enum FlagsA: u8 { Foo, Bar, Baz, } enum FlagsB: c_int { Foo, Bar, Baz, } } }
Hashmap's friends
Recipe | Crates | Categories |
---|---|---|
Insertion-ordered map | ||
Multimap | ||
Slotmap |
Insertion-ordered map
A HashMap that seperately keeps track of insertion order and allows you to efficiently iterate over its elements in that order
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.
See also
Stack-allocated arrays
arrayvec
Arrays that are ONLY stack-allocated with fixed capacity.
smallvec
Arrays that are stack-allocated with fallback to the heap if the fixed stack capacity is exceeded.
tinyvec
Stack-allocated arrays in 100% safe Rust code. tinyvec
requires items to implement the Default trait.
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.
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 |
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 |
---|---|---|
Elasticsearch | ||
Infisearch | ||
Stork search | ||
Minisearch | ||
Typesense | {{hi:typesense}} | |
Tinysearch | {{hi:tinysearch}} |
Query Builders and ORMs
SQLite
Recipe | Crates | Categories |
---|---|---|
Create a SQLite database | ||
Insert and Select data | ||
Using transactions |
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 rusqlite::Connection; use rusqlite::Result; pub fn main() -> Result<()> { 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. )?; 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 rusqlite::Connection; use rusqlite::Result; pub fn main() -> Result<()> { let mut conn = Connection::open("temp/cats.db")?; successful_tx(&mut conn)?; let res = rolled_back_tx(&mut conn); assert!(res.is_err()); 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() } 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() }
Working with Postgres
Recipe | Crates | Categories |
---|---|---|
Create tables in a Postgres database | ||
Insert and Query data | ||
Aggregate data | ||
tokio-postgres | ||
Cornucopia for postgres |
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::Error; use postgres::NoTls; fn main() -> Result<(), Error> { let mut client = Client::connect( "postgresql://postgres:postgres@localhost/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 ) ", )?; 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, } fn main() -> Result<(), Error> { let mut client = Client::connect( "postgresql://postgres:postgres@localhost/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, } fn main() -> Result<(), Error> { let mut client = Client::connect( "postgresql://postgres:postgres@127.0.0.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.
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.
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.
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.
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.
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.
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.
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.
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 anyhow::Result; use redis::Commands; fn fetch_an_integer() -> redis::RedisResult<isize> { // `open` does not actually open a connection yet but it does perform some // basic checks on the URL that might make the operation fail. let client = redis::Client::open("redis://127.0.0.1/")?; // actually connect to redis let mut con = client.get_connection()?; // 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. con.get("my_key") } fn main() -> Result<()> { let my_int = fetch_an_integer()?; println!("{}", my_int); Ok(()) }
Search
Recipe | Crates | Categories |
---|---|---|
Elasticsearch | ||
Infisearch | ||
Stork search | ||
Minisearch | ||
Typesense | {{hi:typesense}} | |
Tinysearch | {{hi:tinysearch}} |
Elasticsearch
Infisearch
Stork search
Minisearch
minisearch-client-side-fulltext-search-engine
Typesense
Tinysearch
A Tiny, Static, Full-Text Search Engine using Rust and WebAssembly
Date and Time
Manage the complexity of dealing with the fourth dimension.
There are two key libraries:
-
time
: a smaller, simpler library. Preferrable if covers your needs, but it's quite limited in what it provides. -
chrono
: the most comprehensive and full-featured datetime library, but more complex because of it.
Unfortunately 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 elapsed time | ||
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 elapsed time | ||
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::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(Duration::max_value()) { Some(x) => println!("{}", x), None => eprintln!( "We can't use chrono to tell the time for the Solar System to complete more than one full orbit around the galactic center." ), } }
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
⮳.
#![allow(unused)] fn main() { }
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]
.
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 |
Recipe | Crates | Categories |
---|---|---|
Package layout |
Documentation
Recipe | Crates | Categories |
---|---|---|
Documenting your code | ||
Module or crate-level documentation |
Recipe | Crates | Categories |
---|---|---|
mdBook | ||
mdbook plugins | ||
CD / CI | ||
Rust playground |
Formatting
Recipe | Crates | Categories |
---|---|---|
Rustfmt | ||
Rustfmt Configuration | ||
Formatting attributes |
Installation
Recipe | Crates | Categories |
---|---|---|
Rustup | ||
Cargo install | ||
Cargo binstall |
Recipe | Crates | Categories |
---|---|---|
Key commands |
Other
Recipe | Crates | Categories |
---|---|---|
Miri | ||
Miri installation |
Recipe | Crates | Categories |
---|---|---|
Installation in a dev container | ||
Example justfile |
Recipe | Crates | Categories |
---|---|---|
Background code checker | ||
API search | roogle{{hi:roogle}} | |
Deployment on shuttle.rs | shuttle.rs | |
Binary minimizer | ||
Code generators | ||
Code verifier | ||
Formal Methods |
Versioning
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)
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.
flamegraph as an easy way to generate flamegraphs for visualizing performance profiles of Rust programs.
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.
just as a Rust-based equivalent to make without the "have files changed" detection but with significantly fewer syntactic warts. (See cargo-make if you want something with a bulkier syntax but more cross-platform portability)
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.
cargo-about, cargo-deny, cargo-license, or cargo-lichking for license compliance management
cargo-audit and cargo-sweep
cargo-spellcheck
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 in crates.io
time = "0.1.12"
# This is equivalent to the ^0.1.12 SemVer version range.
# `cargo update -p 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 |
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
⮳
Installing
Recipe | Crates | Categories |
---|---|---|
Rustup | ||
Cargo install | ||
Cargo binstall |
Rustup
Rustup installs, manages, and upgrades versions of rustc, cargo, clippy, rustfmt, etc.
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.
Cargo binstall
Binstall provides a low-complexity mechanism for installing Rust binaries as an alternative to building from source (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.
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 |
---|---|---|
Key commands |
rustup
⮳ is a toolchain multiplexer. It installs and manages many 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.
This is similar to Python's pyenv
⮳ or Node's nvm
⮳.
Key commands
rustup help
# Show the help page for a subcommand (like toolchain)
rustup toolchain help
# Open the local documentation in your browser
rustup doc
# Update to a new version of Rust
rustup update
# Show which toolchain will be used in the current directory
rustup show
# 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
See also
Text Editors
Applications for editing text.
Recipe | Crates | Categories |
---|---|---|
VS Code | VS Code | |
IntelliJ Rust | ||
Zed | ||
Helix Editor |
Formatting and Linting
Recipe | Crates | Categories |
---|---|---|
Rustfmt | ||
Rustfmt Configuration | ||
Formatting attributes |
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
Rustfmt Configuration
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
Formatting attributes
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 |
---|---|---|
Documenting your code | ||
Module or crate-level documentation |
Documenting 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(), } } struct Person { name: String, } fn main() { let _ = new("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.
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.
mdBook
Recipe | Crates | Categories |
---|---|---|
mdBook | ||
mdbook plugins | ||
CD / CI | ||
Rust playground |
mdBook
mdBook⮳: a utility to create modern online books from Markdown files.
cargo install mdbook<a name="a003"></a>
cargo install mdbook-hide # optional plugin; many others exist
mdbook serve --open
Playground
mdbook
plugins
- mdbook third-party plugins
- mdbook-private
- mdbook-linkcheck
- A runner for
mdbook
s to keep your documentation tested: Byron-termbook
CD / CI
- consider mdbook-cmdrun A mdbook preprocessor for runnning arbitrary (shell) commands in a markdown file
- consider mdbook-journal or mdbook-tera for templating
- review c-mdbook_toc-github A preprocessor for mdbook to add inline Table of Contents support.
- add page TOC ? mdbook-theme
- alternative
- mdbook-toc
yapp
A mdBook preprocessor for simple text replacements
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.
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, } ); // TODO increment_*() no longer exist; remove example? // 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); Ok(()) }
Check if 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")?; let version_2 = Version::parse("1.0.0")?; assert!(!version_1.pre.is_empty()); assert!(version_2.pre.is_empty()); Ok(()) }
Find the latest version satisfying 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::anyhow; use anyhow::bail; use anyhow::Context; use anyhow::Result; 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 ); } Ok(()) }
Others
Recipe | Crates | Categories |
---|---|---|
Background code checker | ||
API search | roogle{{hi:roogle}} | |
Deployment on shuttle.rs | shuttle.rs | |
Binary minimizer | ||
Code generators | ||
Code verifier | ||
Formal Methods |
Background code checker
bacon
is a background rust code checker.
It's 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.
API search
Deployment
Binary minimizer
How to minimize Rust binary size min-sized-rust⮳
Code generators
top-artificial-intelligence-ai-tools-that-can-generate-code-to-help-programmers⮳
Code verifier
kani⮳ is a Rust verifier.
Formal Methods
rust-formal-methods.github.io⮳
Miri Interpreter
Recipe | Crates | Categories |
---|---|---|
Miri | ||
Miri installation |
Miri
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 also perform cross-interpretation for arbitrary foreign targets.
Installation
rustup +nightly component add miri
cargo clean
cargo miri test
# or
cargo miri run
Just
Recipe | Crates | Categories |
---|---|---|
Installation in a dev container | ||
Example justfile |
just
is a command runner / Make replacement.
Installation 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
⮳:
# Just: https://just.systems/man/en/
RUN apk add just
Example justfile
Place it 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!"
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 |
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
#![allow(unused)] fn main() { }
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
#![allow(unused)] fn main() { }
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++)
#![allow(unused)] fn main() { }
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++)
#![allow(unused)] fn main() { }
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)
#![allow(unused)] fn main() { }
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)
#![allow(unused)] fn main() { }
Cargo Plugins
Subcommands that extend the capabilities of Cargo.
Writing code
Recipe | Crates | Categories |
---|---|---|
Templates | Cargo Generate | |
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 | ||
Format | ||
Lint your code | ||
Fix compiler warnings | ||
Check your code before committing it |
Building
Recipe | Crates | Categories |
---|---|---|
cargo make | ||
Devx | ||
xshell: Making Rust a Better Bash |
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
See also
- Testing
- Build Utils
- Debugging
- FFI
- Procedural Macro Helpers
- Profiling
Write code
Recipe | Crates | Categories |
---|---|---|
Templates | Cargo Generate | |
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 | ||
Format | ||
Lint your code | ||
Fix compiler warnings | ||
Check 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
Check 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 | ||
Devx | ||
xshell: Making Rust a Better Bash |
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.
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.
xshell: Making Rust a Better Bash
xshell
⮳ provides a set of cross-platform utilities for writing cross-platform and ergonomic "bash" scripts.
Cross-compile
Recipe | Crates | Categories |
---|---|---|
Cross-compile using Zig as the linker |
Cross-compile using Zig as the 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
Edit Cargo.toml
Cargo commands for modifying a Cargo.toml
file
This tool extends Cargo to allow 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.
Debugging, Logging
Help you figure out what is going on with your code such as logging, tracing, or assertions.
Tracing
Recipe | Crates | Categories |
---|---|---|
Tracing initialization | ||
Basic tracing | ||
Combine layers | ||
Configure a custom event formatter | ||
Events | ||
Spans | ||
Add tracing spans to functions | ||
Related crates |
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 |
---|---|---|
Older alternatives to tracing | ||
log | ||
slog | ||
log4rs | ||
env_logger | ||
Other frameworks | ||
OpenTelemetry | ||
OpenObserve |
Diagnostic functions
Recipe | Crates | Categories |
---|---|---|
Get the type name of the pointed-to value |
Logs
Recipe | Crates | Categories |
---|---|---|
Tracing initialization | ||
Basic tracing | ||
Combine layers | ||
Configure a custom event formatter | ||
Events | ||
Spans | ||
Add tracing spans to functions | ||
Related crates |
Add to Cargo.toml
[dependencies]
tracing = "0.1"
tracing-subscriber = "0.3"
Initialization
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();
}
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();
}
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::debug; use tracing::error; use tracing::event; use tracing::info; use tracing::trace; use tracing::warn; use tracing::Level; #[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::span; use tracing::Level; fn main() { let span = span!(Level::TRACE, "my_span"); { // current lexical scope. let _guard = span.enter(); // `enter` returns a RAII guard // which, when dropped, exits the // span. // Any trace events that occur here // will occur within the span. } // Dropping the guard exits the span. }
One-liner with .entered()
:
use tracing::span; use tracing::Level; fn main() { let span = span!(Level::TRACE, "some span").entered(); // code here is within the span // optionally, explicitly exit the span, returning it let span = span.exit(); // code here is no longer within the span // enter the span again let _span = span.entered(); }
Holding the drop guard returned by Span::enter
across .await
points will result in incorrect traces. Use tracing::span::Span::in_scope
⮳.
use tracing::debug_span; use tracing::info_span; use tracing::Instrument; 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::event; use tracing::instrument; use tracing::Level; #[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; }
Related crates
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")] use syslog::Error; #[cfg(target_os = "linux")] use syslog::Facility; #[cfg(target_os = "linux")] fn main() -> Result<(), Error> { 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() { println!("So far, only Linux systems are supported."); }
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 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 |
---|---|---|
Older alternatives to tracing | ||
log | ||
slog | ||
log4rs | ||
env_logger | ||
Other frameworks | ||
OpenTelemetry | ||
OpenObserve |
Older alternatives to tracing
Tracing is now the "go-to" crate for logging.
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.
log4rs
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⮳
OpenObserve
openobserve⮳ (written in Rust) is a petabyte-scale Elasticsearch/Splunk/Datadog alternative for logs, metrics, traces, RUM, error tracking, and session replay.
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
DevOps and Rust
DevOps
Recipe | Crates | Categories |
---|---|---|
Daytona |
Dependency Management
Recipe | Crates | Categories |
---|---|---|
deps.rs | ||
Rust Digger |
Git Hooks
Recipe | Crates | Categories |
---|---|---|
Check your code before committing it | ||
pre-commit |
GitHub Actions
Recipe | Crates | Categories |
---|---|---|
Installation of development tools | ||
Compilation caching |
Release Automation
Recipe | Crates | Categories |
---|---|---|
cargo release | ||
release-plz |
DevOps + Rust
Recipe | Crates | Categories |
---|---|---|
Daytona |
Daytona
Daytona is a self-hosted and secure open source development environment manager. Daytona's support for Dev Containers standard enables developers to tailor their environments to exact specifications and share configurations for efficient collaboration
DIY Guide to Transform Any Machine into a Codespace⮳
Dependency management
Recipe | Crates | Categories |
---|---|---|
deps.rs | ||
Rust Digger |
deps.rs
deps.rs
⮳ (github)⮳ uses semantic versioning to detect outdated or insecure dependencies in your project'sCargo.toml
.
Rust Digger
rust-digger.code-maven.com⮳ collects data about Rust Crates⮳ to find the common practices of Open Source Rust developers and trying to improve the Rust ecosystem.
Git hook scripts
Recipe | Crates | Categories |
---|---|---|
Check your code before committing it | ||
pre-commit |
Git hook scripts are useful for automatically identifying simple issues, such as missing semicolons, trailing whitespace, poor formatting of the code or configuration files, when commiting in git
, prior to submission to code review or start of a CI workflow.
Check your code before committing it
cargo-husky⮳ setup Git hooks automatically for cargo projects with 🐶
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
pre-commit
pre-commit
⮳ is a Python framework for managing and maintaining multi-language pre-commit hooks.
Installation
pre-commit
is written in Python. Include the following into your Dockerfile
or run the commands by hand to install pre-commit
:
# Install python3, pipx, pre-commit (Ubuntu & friends)
RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
&& apt-get install -y python3 pipx \
&& pipx install pre-commit \
&& pipx ensurepath
- Verify that it is properly installed:
pre-commit --version
- Add a file called
.pre-commit-config.yaml
to the root of your project. Usepre-commit sample-config
for a template. - Edit it to configure your preferred hooks.
# Set up the git hook scripts
pre-commit install
# It's usually a good idea to run the hooks against all of the files when adding new hooks
# (pre-commit will only run on the changed files during git hooks)
pre-commit run --all-files
Useful links
Rust CI Tooling: Clippy, commitlint, pre‑commit
A pre-commit hook for commitlint
GitHub Actions
Recipe | Crates | Categories |
---|---|---|
Installation of development tools | ||
Compilation caching |
GitHub Action for installing development tools (mainly from GitHub Releases).
Installation of development tools
- name: Install cargo check tools
run: |
cargo install --locked cargo-deny || true
cargo install --locked cargo-outdated || true
cargo install --locked cargo-udeps || true
cargo install --locked cargo-audit || true
cargo install --locked cargo-pants || true
- name: Check dependencies
run: |
cargo deny check
cargo outdated --exit-code 1
cargo udeps
rm -rf ~/.cargo/advisory-db
cargo audit
cargo pants
install-action
⮳ is a GitHub Action for installing development tools (mainly from GitHub Releases).
- uses: taiki-e/install-action@v2
with:
tool: cargo-binstall,just,mdbook,mdbook-lintcheck
Compilation caching
Swatinem/rust-cache
is the current recommended cache action, which handles properly optimizing cache effectiveness for a cargo build in CI. That action also automatically sets CARGO_INCREMENTAL=0
for users of the action.
Example .github/workflows/<name>.yml
- uses: actions/checkout@v4
# Selecting a toolchain either by action or manual `rustup` calls. That should happen before the plugin, as the cache uses the current rustc version as its cache key
- run: rustup toolchain install stable --profile minimal
- uses: Swatinem/rust-cache@v2
with:
# The prefix cache key, this can be changed to start a new cache manually.
# default: "v0-rust"
prefix-key: ""
# A cache key that is used instead of the automatic `job`-based key,
# and is stable over multiple jobs.
# default: empty
shared-key: ""
# An additional cache key that is added alongside the automatic `job`-based
# cache key and can be used to further differentiate jobs.
# default: empty
key: ""
# A whitespace separated list of env-var *prefixes* who's value contributes
# to the environment cache key.
# The env-vars are matched by *prefix*, so the default `RUST` var will
# match all of `RUSTC`, `RUSTUP_*`, `RUSTFLAGS`, `RUSTDOC_*`, etc.
# default: "CARGO CC CFLAGS CXX CMAKE RUST"
env-vars: ""
# The cargo workspaces and target directory configuration.
# These entries are separated by newlines and have the form
# `$workspace -> $target`. The `$target` part is treated as a directory
# relative to the `$workspace` and defaults to "target" if not explicitly given.
# default: ". -> target"
workspaces: ""
# Additional non workspace directories to be cached, separated by newlines.
cache-directories: ""
# Determines whether workspace `target` directories are cached.
# If `false`, only the cargo registry will be cached.
# default: "true"
cache-targets: ""
# Determines if the cache should be saved even when the workflow has failed.
# default: "false"
cache-on-failure: ""
# Determines which crates are cached.
# If `true` all crates will be cached, otherwise only dependent crates will be cached.
# Useful if additional crates are used for CI tooling.
# default: "false"
cache-all-crates: ""
# Determiners whether the cache should be saved.
# If `false`, the cache is only restored.
# Useful for jobs where the matrix is additive e.g. additional Cargo features,
# or when only runs from `master` should be saved to the cache.
# default: "true"
save-if: ""
# To only cache runs from `master`:
save-if: ${{ github.ref == 'refs/heads/master' }}
# Specifies what to use as the backend providing cache
# Can be set to either "github" or "buildjet"
# default: "github"
cache-provider: ""
rust-toolchain (github action) Github action to install Rust components via rustup
rust-cache (github action) Github action to cache compilation artifacts and speed up subsequent runs.
install-action (github action) GitHub Action for installing development tools (mainly from GitHub Releases).
Release automation
Recipe | Crates | Categories |
---|---|---|
cargo release | ||
release-plz |
cargo release
Helper for publishing new crate versions.
release-plz
Release Rust crates from CI with a Release PR.
Continuous Deployment / Continuous Integration
Recipe | Crates | Categories |
---|---|---|
CD / CI |
Continuous Integration (cargo book)⮳
See also
Creating a docker container action⮳
"Upload a Build Artifact" Github Action⮳
Cache management with GitHub Actions⮳
How to cache docker-compose build inside github-action⮳
Optimizing CI/CD pipelines in your Rust projects⮳
GitHub Action for installing development tools (mainly from GitHub Releases)⮳
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 hex | ||
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 |
---|---|---|
JSON | ||
serde-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 hex | ||
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::percent_decode; use percent_encoding::utf8_percent_encode; use percent_encoding::AsciiSet; use percent_encoding::CONTROLS; /// 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(); assert_eq!(encoded, "confident,%20productive%20systems%20programming"); let iter = percent_decode(encoded.as_bytes()); let decoded = iter.decode_utf8()?; 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 hex
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); assert_eq!(encoded, expected); let decoded = HEXUPPER.decode(&encoded.into_bytes())?; 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 = BASE64_STANDARD.encode(hello); let decoded = 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 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::anyhow; use anyhow::Result; use serde::de; use serde::Deserialize; use serde::Deserializer; #[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::json; use serde_json::Error; use serde_json::Value; fn main() -> Result<(), Error> { let j = r#"{ "userid": 103609, "verified": true, "access_privileges": [ "user", "admin" ] }"#; let parsed: Value = serde_json::from_str(j)?; let expected = json!({ "userid": 103609, "verified": true, "access_privileges": [ "user", "admin" ] }); assert_eq!(parsed, expected); Ok(()) }
Deserialize a TOML configuration file
Parse some TOML into a universal toml::Value
that is able to represent any valid TOML data.
use toml::de::Error; use toml::Value; 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: Value = toml::from_str(toml_content)?; assert_eq!(package_info["dependencies"]["serde"].as_str(), Some("1.0")); assert_eq!( package_info["package"]["name"].as_str(), Some("your_package") ); Ok(()) }
Parse TOML into your own structs using serde
⮳.
use std::collections::HashMap; use serde::Deserialize; use toml::de::Error; #[derive(Deserialize)] struct Config { package: Package, dependencies: HashMap<String, String>, } #[derive(Deserialize)] 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)?; 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
⮳ can reverse the significant bytes of structured data. This may be necessary when receiving information over the network, such that bytes received are from another system.
use std::io::Error; use byteorder::LittleEndian; use byteorder::ReadBytesExt; use byteorder::WriteBytesExt; #[derive(Default, PartialEq, Debug)] struct Payload { kind: u8, value: u16, } fn main() -> Result<(), Error> { let original_payload = Payload::default(); let encoded_bytes = encode(&original_payload)?; let decoded_payload = decode(&encoded_bytes)?; assert_eq!(original_payload, decoded_payload); Ok(()) } 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) }
Serialization
Recipe | Crates | Categories |
---|---|---|
JSON | ||
serde-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.
JSON
See also
monostate
This library implements a type macro for a zero-sized type that is Serde deserializable only from one specific value.
serde-ignored
Typecasts
bytemuck
zerocopy
Error Handling
Irrecoverable panics
fn main() { panic!("crash and burn"); }
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(); }
unwrap_or_else
## A Shortcut for propagating errors: the ? Operator {#question-mark-operator}
[![cat-rust-patterns][cat-rust-patterns-badge]][cat-rust-patterns]<a name="a004"></a>
```rust
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() {
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>> { // needed 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). let port: u16 = s.parse()?; if port == 0 { Err(Box::from(format!("invalid: {}", 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
Handles error that occur when trying to open a file that does not exist. It is achieved by using error_chain
⮳, a library that takes care of a lot of boilerplate code needed in order to handle errors in Rust⮳.
Io(std::io::Error)
inside foreign_links
⮳ allows automatic conversion from std::io::Error
⮳ into error_chain::error_chain
⮳ 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. Returns uptime unless there is an error.
Other recipes in this book will hide the error_chain
⮳ boilerplate, and can be seen by expanding the code with the ⤢ button.
use std::fs::File; use std::io::Read; use anyhow::anyhow; use anyhow::Result; 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
The error_chain
⮳ crate makes matching
⮳ on different error types returned by a function possible and relatively compact. error_chain::example_generated::ErrorKind
⮳ determines the error type.
Uses reqwest
⮳::blocking⮳ to query a random integer generator web service. Converts the string response into an integer. The Rust standard library,
reqwest
⮳, and the web service can all generate errors. Well defined Rust errors use foreign_links
⮳ An additional error_chain::example_generated::ErrorKind
⮳ variant for the web service error uses errors
block of the error_chain!
macro.
fn parse_response( response: reqwest::blocking::Response, ) -> anyhow::Result<u32> { let body = response.text()?; let body = body.trim(); // println!("{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 backtrace of complex error scenarios
This recipe shows how to handle a complex error scenario and then print a backtrace. It relies on chain_err
⮳ to extend errors by appending new errors. The error stack can be unwound, thus providing a better context to understand why an error was raised.
The below recipes attempts to deserialize the value 256
into a
u8
⮳. An error will bubble up from Serde then csv and finally up to the user code.
// TODO rewrite // 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() {}
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 error_chain::ChainedError::backtrace
⮳ associated with this error.
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
Use Result<T, anyhow::Error>
or equivalently anyhow::Result<a name="a005"></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 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.
#![allow(unused)] fn main() { #[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
prints fancy diagnostics upon error.
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
A fork of anyhow
that gives you more control over the format of the generated error messages. Recommended if you intend to present error messages to end users. Otherwise anyhow
is simpler.
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 a 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 |
---|---|---|
Get platform-specific locations for configuration, cache, and other data with dirs | ||
Get platform-specific locations for configuration, cache, and other data with directories |
Temporary Files and Directories
Recipe | Crates | Categories |
---|---|---|
Create temporary files or temporary directories |
Directory Traversal
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 a 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::File; use std::io::BufRead; use std::io::BufReader; use std::io::Error; use std::io::Write; fn main() -> Result<(), Error> { 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::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> { 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::File; use std::io::Error; use std::io::Write; use memmap2::Mmap; fn main() -> Result<(), Error> { 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)? }; 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
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::anyhow; use anyhow::Result; 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
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.
use std::io; use std::path::Path; use std::path::PathBuf; use same_file::is_same_file; fn contains_loop<P: AsRef<Path>>( path: P, ) -> io::Result<Option<(PathBuf, PathBuf)>> { let path = path.as_ref(); let mut path_buf = path.to_path_buf(); while path_buf.pop() { if is_same_file(&path_buf, path)? { return Ok(Some((path_buf, path.to_path_buf()))); } else if let Some(looped_paths) = contains_loop(&path_buf)? { return Ok(Some(looped_paths)); } } Ok(None) } fn main() { assert_eq!( contains_loop("/tmp/foo/bar/baz/qux/bar/baz").unwrap(), Some(( PathBuf::from("/tmp/foo"), PathBuf::from("/tmp/foo/bar/baz/qux") )) ); }
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 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() { WalkDir::new(".") .into_iter() .filter_entry(is_not_hidden) .filter_map(|v| v.ok()) .for_each(|x| println!("{}", x.path().display())); }
Recursively calculate file sizes at 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 png files 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::glob_with; use glob::MatchOptions; 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(()) }
High-performance globbing that allows multiple globs to be evaluated at once
Walk the filesystem
Recipe | Crates | Categories |
---|---|---|
Walk the filesystem while respecting ignore files |
Walk the filesystem while respecting ignore files
Recursive filesystem walking that respects ignore files (like .gitignore)
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
use std::path::Path; use notify::event::Event; use notify::EventHandler; use notify::RecursiveMode; use notify::Result; use notify::Watcher; /// 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
Supports both temporary files and temporary directories.
User directories
Recipe | Crates | Categories |
---|---|---|
Get platform-specific locations for configuration, cache, and other data with dirs | ||
Get platform-specific locations for configuration, cache, and other data with directories |
Get platform-specific locations for configuration, cache, and other data
dirs
Provide platform-specific locations for configuration, cache, and other data
directories
A higher-level library that can also compute paths for applications
A library that provides config/cache/data paths, following the respective conventions on Linux, macOS and Windows
GPU Programming
Recipe | Crates | Categories |
---|---|---|
rust-gpu |
Use GPUs with Rust
Recipe | Crates | Categories |
---|---|---|
rust-gpu |
rust-gpu
Hardware Support
Interface with specific CPU or other hardware features.
Processor
Recipe | Crates | Categories |
---|---|---|
Get the number of logical cpu cores |
Processor
Recipe | Crates | Categories |
---|---|---|
Get the number of logical cpu cores |
Check number of logical cpu cores
Shows the number of logical CPU cores in current machine using num_cpus::get
⮳.
fn main() { println!("Number of logical cores is {}", num_cpus::get()); }
Machine Learning
Linfa
Candle
Others
Smartcore
SmartCore⮳ Machine Learning in Rust.
Watchmaker
Watchmaker (genetic algos in Rust)⮳
Mathematics
Crates with a mathematical aspect.
Linear algebra
Recipe | Crates | Categories |
---|---|---|
Vector Norm | ||
Adding matrices | ||
Multiplying matrices | ||
Multiply a scalar with a vector with a matrix | ||
Invert matrix | ||
Vector comparison | ||
(De)-Serialize a Matrix |
Trigonometry
Recipe | Crates | Categories |
---|---|---|
Calculating the side length of a triangle | ||
Verifying tan is equal to sin divided by cos | ||
Distance between two points on the Earth |
Complex numbers
Recipe | Crates | Categories |
---|---|---|
Creating complex numbers | ||
Adding complex numbers | ||
Mathematical functions on complex numbers |
Statistics
Recipe | Crates | Categories |
---|---|---|
Computing standard deviation | ||
Measures of central tendency | ||
Standard deviation |
Additional numeric types
Recipe | Crates | Categories |
---|---|---|
Abstracting over different number types | ||
Big integers | ||
Big decimal | ||
Sortable Floats |
Linear Algebra
Recipe | Crates | Categories |
---|---|---|
Vector Norm | ||
Adding matrices | ||
Multiplying matrices | ||
Multiply a scalar with a vector with a matrix | ||
Invert matrix | ||
Vector comparison | ||
(De)-Serialize a Matrix |
Adding matrices
Creates two 2-D matrices with ndarray::arr2
⮳ and sums them element-wise.
Note the sum is computed as let sum = &a + &b
. The &
operator is used to avoid consuming a
and b
, making them available later for display. A new array is created containing their sum.
use ndarray::arr2; fn main() { let a = arr2(&[[1, 2, 3], [4, 5, 6]]); let b = arr2(&[[6, 5, 4], [3, 2, 1]]); let sum = &a + &b; println!("{}", a); println!("+"); println!("{}", b); println!("="); println!("{}", sum); }
Multiplying 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::arr1; use ndarray::arr2; use ndarray::Array1; 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); }
Vector comparison
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.])); }
Vector norm
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::array; use ndarray::Array1; use ndarray::ArrayView1; 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 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); // 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 |
---|---|---|
Calculating the side length of a triangle | ||
Verifying tan is equal to sin divided by cos | ||
Distance between two points on the Earth |
Calculating 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); }
Verifying 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(); assert_eq!(a, b); }
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 |
---|---|---|
Creating complex numbers | ||
Adding complex numbers | ||
Mathematical functions on complex numbers |
Creating 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); }
Adding 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); }
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 |
---|---|---|
Computing standard deviation | ||
Measures of central tendency | ||
Standard deviation |
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 (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); }
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 |
---|---|---|
Abstracting over different number types | ||
Big integers | ||
Big decimal | ||
Sortable Floats |
Abstracting over different number types
Traits like Number, Add, etc that allow you write functions that are generic over the specific numeric type
Big Integers
num
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
It's not the fastest, but it's part of the trusted num library.
rug
LGPL licensed. Wrapper for GMP. Much faster than num-bigint
Big decimal
The binary representation consists of a 96 bit integer number, a scaling factor used to specify the decimal fraction and a 1 bit sign.
Sortable 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)).
Memory Management
Deal with allocation, memory mapping, garbage collection, reference counting, or interfaces to foreign memory managers.
Recipe | Crates | Categories |
---|---|---|
Declare lazily evaluated constant |
Recipe | Crates | Categories |
---|---|---|
std | ||
once_cell | ||
lazy_static |
Global static
Recipe | Crates | Categories |
---|---|---|
Declare lazily evaluated constant |
Declare lazily evaluated constant
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()); 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
Operating System
Bindings to operating system-specific APIs.
Recipe | Crates | Categories |
---|---|---|
Call libc |
Recipe | Crates | Categories |
---|---|---|
Bottlerocket | ||
Redox |
External Command
Run an external command and process 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::bail; use anyhow::Result; 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 and check for an error code
Opens the python
interpreter using an external std::process::Command
⮳ and passes it a python statement for execution. 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::anyhow; use anyhow::bail; use anyhow::Result; 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.
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()?; if let Some(sort_output) = sort_output_child.stdout.take() { 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 stdout and stderr of 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::File; use std::process::Command; use std::process::Stdio; 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 child process' outputs
In Run an external command and process stdout, processing doesn't start until 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 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.rs⮳ is a library for running child processes. Duct makes it easy to build pipelines and redirect IO 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 |
Call libc
Bindings for directly calling libc functions.
Operating systems written in Rust
Recipe | Crates | Categories |
---|---|---|
Bottlerocket | ||
Redox |
Bottlerocket
bottlerocket⮳ is an operating system designed for hosting containers.
Redox
redox⮳ is a Rust Operating System.
Rust Patterns
Shared solutions for particular situations specific to programming in Rust.
Recipe | Crates | Categories |
---|---|---|
Anyhow | ||
thisError | ||
miette | ||
color-eyre |
Recipe | Crates | Categories |
---|---|---|
Abstract factory | ||
dyn-clone | ||
Lens |
Recipe | Crates | Categories |
---|---|---|
Compose iterators with itertools |
Recipe | Crates | Categories |
---|---|---|
Rust idioms |
Design Patterns
Recipe | Crates | Categories |
---|---|---|
Abstract factory | ||
dyn-clone | ||
Lens |
Abstract factory
dyn-clone
This 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(); }
Functional programming
Recipe | Crates | Categories |
---|---|---|
Compose iterators with itertools |
Compose iterators with itertools
Extra iterator adaptors, functions and macros.
#![allow(unused)] fn main() { use itertools::assert_equal; use itertools::chain; use itertools::Itertools; // 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 use itertools::cloned; assert_eq!(cloned(b"abc").next(), Some(b'a')); // dedup 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]) }
Rust idioms and patterns
Recipe | Crates | Categories |
---|---|---|
Rust idioms |
Scripting in Rust
Recipe | Crates | Categories |
---|---|---|
Embed Rust scripting in your application |
Rust scripting
Recipe | Crates | Categories |
---|---|---|
Embed Rust scripting in your application |
Embed Rust scripting in your application
rhai⮳ is an embedded scripting language and evaluation engine for Rust that gives a safe and easy way to add scripting to any application.
Template Engine
Crates designed to combine templates with data to produce result documents, usually with an emphasis on processing tex
Tera
Recipe | Crates | Categories |
---|---|---|
Create HTML files from a template |
TinyTemplate
Recipe | Crates | Categories |
---|---|---|
tinytemplate |
Tera
Recipe | Crates | Categories |
---|---|---|
Create HTML files from a template |
Create HTML files from a template
Template engine based on Jinja2/Django templates
Tinytemplate
Recipe | Crates | Categories |
---|---|---|
tinytemplate |
tinytemplate
Text Editors
Applications for editing text.
Recipe | Crates | Categories |
---|---|---|
VS Code | VS Code | |
IntelliJ Rust | ||
Zed | ||
Helix Editor |
IDEs
Recipe | Crates | Categories |
---|---|---|
VS Code | VS Code | |
IntelliJ Rust | ||
Zed | ||
Helix Editor |
VS Code
IntelliJ Rust
If you don’t have a JetBrains license, IntelliJ
IDEA is available for free and supports IntelliJ Rust. If you have a JetBrains license, CLion
is your go-to editor for Rust in JetBrains’ IDE suite.
Zed
Helix Editor
See also
Recipe | Crates | Categories |
---|---|---|
VS Code | VS Code | |
IntelliJ Rust | ||
Zed | ||
Helix Editor |
Text Processing
Deal with the complexities of human language when expressed in textual form.
Recipe | Crates | Categories |
---|---|---|
Collect Unicode Graphemes | ||
Implement the FromStr trait for a custom struct |
Recipe | Crates | Categories |
---|---|---|
Benchmark |
Regular Expressions
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() { assert_eq!(extract_login(r"I❤email@example.com"), Some(r"I❤email")); assert_eq!( extract_login(r"sdf+sdsfsd.as.sdsd@jhkk.d.rl"), Some(r"sdf+sdsfsd.as.sdsd") ); assert_eq!(extract_login(r"More@Than@One@at.com"), None); assert_eq!(extract_login(r"Not an email@email"), None); }
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); 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<'a> fmt::Display for PhoneNumber<'a> { 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::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); assert_eq!(after, "03/14/2012, 01/15/2013 and 07/05/2014"); }
regex De facto standard regex library. Very fast, but does not support fancier features such as backtracking.
fancy-regex
Use if need features such as backtracking which regex doesn't support
Longer Regex Example
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. // [^!] excludes ! 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 // [text] not preceded by ! or ], not followed by [ or <spaces>[ or ( // 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>>(); 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 |
---|---|---|
Benchmark |
Benchmark
Web Programming
Create applications for the web.
Uniform Resource Locations (URL)
Media Types (MIME)
Recipe | Crates | Categories |
---|---|---|
Get MIME type from string | ||
Get MIME type from filename | ||
Parse the MIME type of a HTTP response |
Scraping Web Pages
Recipe | Crates | Categories |
---|---|---|
Extract all links from a webpage HTML | ||
Check 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 a webpage HTML | ||
Check webpage for broken links | ||
Extract all unique links from a MediaWiki markup |
Extract all links from a webpage HTML
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::anyhow; use anyhow::Result; use url::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(()) } 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) }
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 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(()) } fn build_github_url(path: &str) -> Result<Url, ParseError> { const GITHUB: &str = "https://github.com"; let base = Url::parse(GITHUB).expect("hardcoded URL is known to be valid"); let joined = base.join(path)?; Ok(joined) }
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 MIME type from string | ||
Get MIME type from filename | ||
Parse the MIME type of a HTTP response |
Get MIME type from 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::Mime; use mime::APPLICATION_OCTET_STREAM; 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 MIME type from 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
Requests
Recipe | Crates | Categories |
---|---|---|
Make a HTTP GET request | ||
Set custom headers and URL parameters for a REST request | ||
Async |
APIs
Recipe | Crates | Categories |
---|---|---|
Query the GitHub API | ||
Check if an API resource exists | ||
Create and delete Gist with GitHub API | ||
Consume a paginated RESTful API | ||
Handle a rate-limited API |
Downloads
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
Full-fat HTTP client. Can be used in both synchronous and asynchronous code. Requires tokio runtime.
ureq
Minimal synchronous HTTP client focussed on simplicity and minimising dependencies.
hyper
A low-level HTTP implementation (both client and server). Implements HTTP/1, and HTTP/2. Works best with the tokio async runtime, but can support other runtimes.
Making Requests
Recipe | Crates | Categories |
---|---|---|
Make a HTTP GET request | ||
Set custom headers and URL parameters for a REST request | ||
Async |
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(()) }
Async
A similar approach can be used by including the tokio
⮳ executor to make the main function asynchronous, retrieving the same information.
In this example, tokio::main
⮳ handles all the heavy executor setup and allows sequential code implemented without blocking until .await
.
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(()) }
Calling a Web API
Recipe | Crates | Categories |
---|---|---|
Query the GitHub API | ||
Check if an API resource exists | ||
Create and delete Gist with 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 Gist with 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.
// TODO fix - the API no longer returns a crate_id - need to get // 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; // // TODO fix interaction with https://docs.github.com/en/rest?apiVersion=2022-11-28 // 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(()) }
Downloads
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.
// TODO wrong example? 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::anyhow; use anyhow::bail; use anyhow::Result; use reqwest::header::HeaderValue; use reqwest::header::CONTENT_LENGTH; use reqwest::header::RANGE; use reqwest::StatusCode; 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"))?; 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.
Recipe | Crates | Categories |
---|---|---|
Actix |
Recipe | Crates | Categories |
---|---|---|
Axum |
Recipe | Crates | Categories |
---|---|---|
Loco | ||
Rust on Nails |
Recipe | Crates | Categories |
---|---|---|
CORS |
Recipe | Crates | Categories |
---|---|---|
async-graphql |
Recipe | Crates | Categories |
---|---|---|
tonic |
Recipe | Crates | Categories |
---|---|---|
hyper |
Recipe | Crates | Categories |
---|---|---|
Tower | ||
Tower HTTP | ||
Alternatives |
Recipe | Crates | Categories |
---|---|---|
Static website generator - Zola | ||
Zola themes |
Axum
Recipe | Crates | Categories |
---|---|---|
Axum |
Crates.io example source code (using Axum)⮳
- realworld-rust-axum-sqlx⮳ see also realworld.how⮳
- Axum examples⮳
A minimal and ergonomic framework. An official part of the tokio project. Recommend for most new projects.
Actix
Recipe | Crates | Categories |
---|---|---|
Actix |
Auth Web Microservice with rust using actix_web 4.0 - Complete Tutorial⮳
Practical Rust Web Development - API Rest⮳
- actix⮳
- realworld-actix-fullstack⮳
- realworld-v1-rust-actix-web-diesel⮳
- auth-microservice-rust-actix-web1.0-diesel-complete-tutorial⮳
A performance focussed framework. All Rust frameworks are fast, but choose actix-web if you need the absolutely maximum performance.
Other Web Frameworks
Rust web framework comparison⮳
Rocket
Web framework with a focus on usability, security, extensibility, and speed.
Rust + Rocket RealWorld framework implementation⮳
Leptos
leptos
⮳ is a full-stack, isomorphic Rust web framework leveraging fine-grained reactivity to build declarative user interfaces.
Static Website Generators
Recipe | Crates | Categories |
---|---|---|
Static website generator - Zola | ||
Zola themes |
Zola
Zola⮳ is a fast static site generator in a single binary with everything built-in.
Zola Themes
AdiDoks is a modern documentation theme.
Middleware
Recipe | Crates | Categories |
---|---|---|
Tower | ||
Tower HTTP | ||
Alternatives |
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::header::HeaderName;
use http::header::AUTHORIZATION;
use http::header::CONTENT_TYPE;
use http::Request;
use http::Response;
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);
}
Alternatives
A modular toolkit for building async web apps
CORS
Recipe | Crates | Categories |
---|---|---|
CORS |
Using the Tower ecosystem:
use std::convert::Infallible;
use anyhow::Result;
use bytes::Bytes;
use http::header;
use http::Method;
use http::Request;
use http::Response;
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?;
assert_eq!(
response
.headers()
.get(header::ACCESS_CONTROL_ALLOW_ORIGIN)
.unwrap(),
"*",
);
Ok(())
}
Batteries-included frameworks
Recipe | Crates | Categories |
---|---|---|
Loco | ||
Rust on Nails |
Loco
Rust on Nails
See also
Building a SaaS with Rust and Next.js⮳
GraphQL
Recipe | Crates | Categories |
---|---|---|
async-graphql |
async-graphql
A high-performance graphql server library that's fully specification compliant. Integrates with actix-web, axum, poem, rocket, tide, warp.
gRPC
Recipe | Crates | Categories |
---|---|---|
tonic |
tonic
gRPC over HTTP/2 with full support for asynchronous code. Works with tokio
Hyper
Recipe | Crates | Categories |
---|---|---|
hyper |
hyper
hyper
is a low-level HTTP implementation (both client and server). Implements HTTP/1, and HTTP/2. Works best with the tokio async runtime, but can support other runtimes.
Tools built in Rust
Development Tools
Recipe | Crates | Categories |
---|---|---|
Compilers | ||
swc | ||
Code editors | ||
lapce | ||
zed | ||
Build tools |
Python tools written in Rust
Recipe | Crates | Categories |
---|---|---|
rustpython | ||
pyOxidizer | ||
Ruff | ||
uv |
Others
Recipe | Crates | Categories |
---|---|---|
File managers | ||
Remote desktops | ||
Email applications | ||
News | ||
Payments | ||
Social media |
Development tools written in Rust
Recipe | Crates | Categories |
---|---|---|
Compilers | ||
swc | ||
Code editors | ||
lapce | ||
zed | ||
Build tools |
Compilers
swc
swc
(github)⮳ (stands for Speedy Web Compiler
) is a fast TypeScript / JavaScript compiler written in Rust. It's a library for Rust and JavaScript at the same time.
Code editors
Lapce
lapce
⮳ is a lightning-fast and Powerful Code Editor written in Rust.
Zed
Zed⮳ is a high-performance, multiplayer code editor from the creators of Atom and Tree-sitter.
Build tools
pantsbuild.org⮳ is a fast, scalable, user-friendly build system for codebases of all sizes. It's currently focused on Python, Go, Java, Scala, Kotlin, Shell, and Docker.
Tools for Python written in Rust
Recipe | Crates | Categories |
---|---|---|
rustpython | ||
pyOxidizer | ||
Ruff | ||
uv |
rustpython
RustPython
⮳ is a Python Interpreter written in Rust
pyOxidizer
Ruff
Ruff⮳
uv
Other tools written in Rust
Recipe | Crates | Categories |
---|---|---|
File managers | ||
Remote desktops | ||
Email applications | ||
News | ||
Payments | ||
Social media |
File managers
Spacedrive is an open source cross-platform file explorer, powered by a virtual distributed filesystem written in Rust. spacedrive⮳
Remote desktops
rustdesk⮳ is an open-source remote desktop application designed for self-hosting, as an alternative to TeamViewer.
Email applications
postsack⮳ visually cluster your emails by sender, domain, and more to identify waste.
News
headlines⮳ is a simple news reading GUI app built in Rust
Payments
hyperswitch⮳ is an open source payments switch written in Rust to make payments fast, reliable and affordable.
Social media
lemmy⮳ is a link aggregator and forum for the fediverse.
Essential Rust Links
Section |
---|
Learning Rust |
Rust Cheatsheets |
Comparison to other languages |
Books |
Example code |
Rust Blogs & Podcasts |
Newsletters |
Meetups |
Companies |
Lists of Rust links |
Lists of Rust links
Section |
---|
Rust Links |
- The Rust Starter Pack⮳
- : a curated list of Rust code and resources.
Example code
Recipe |
---|
example-code |
- Zero To Production In Rust (book)⮳
- Zero To Production In Rust (code)⮳
- Source Code for 'Practical Rust Web Projects' by Shing Lyu practical-rust-web-projects⮳
- A fullstack RealWorld implementation using rust, axum, sqlx, and yew: realworld-rust-axum-sqlx⮳
- A full template for a REST API written in Rust, engineered for maximum testability: rust-rest⮳
Rust Cheatsheets
Cheatsheets
Comparison to other languages
Blogs, Podcasts, Meetups
Section |
---|
Meetups |
Newsletters |
Podcasts & blogs |
-
LukeMathWalker/zero-to-production: Code for "Zero To Production In Rust", a book on API development using Rust⮳
Rust Podcasts, Blogs, Meetups
Newsletters
- Master Rust easily with our engaging, tutorial-style lessons and real-world examples in our Launchpad newsletter: shuttle.rs/launchpad
Meetups
Rust Meetup Linz
Books
Section |
---|
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⮳
Companies that use or contribute to Rust
Learn Rust
Section |
---|
Rust Learning |
- 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
Index
*, 1
(), 1
.gitignore, 1
Accessibility, 1
actix-web, 1, 2, 3, 4, 5
aes-gcm, 1
aes-gcm-siv, 1
Ai, 1
Algorithms, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15
Allocation, 1
Analytics, 1
ANSI terminals, 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
anyhow, 1, 2, 3, 4
anyhow::Result, 1
Apache Arrow, 1
Apache DataFusion, 1
API bindings, 1, 2, 3
apk, 1
application/x-www-form-urlencoded, 1
approx, 1, 2, 3
approx::assert_abs_diff_eq, 1
Arc, 1
arc-swap, 1
Architecture, 1
argon2, 1
Argument parsers, 1
arrayvec, 1
arrow, 1
asref, 1
Assertions, 1
Async, 1, 2
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
Asynchronous programming, 1
Asynchronous runtime, 1
Atomic types, 1
Attributes, 1, 2, 3
Authentication, 1, 2, 3
Autotraits, 1
await, 1, 2, 3, 4
AWS, 1
axum, 1, 2, 3, 4
bacon, 1, 2, 3, 4
Badges, 1
Base URL, 1
base64, 1, 2, 3
base64::decode, 1
base64::encode, 1
bat, 1, 2, 3
bcrypt, 1
bevy, 1, 2, 3, 4
Binary crate, 1
bitfield, 1
bitflags, 1, 2
bitflags::bitflags, 1
Bitwise operations, 1
Blas, 1
blessed.rs, 1, 2
Blocking code, 1
Blocking operation, 1
Blogs, 1
Books, 1
bool, 1
Borrowing, 1, 2
bottlerocket, 1, 2
Boundary, 1
Box, 1
broot, 1, 2
Build metadata, 1
Build Utils, 1
Build utils, 1, 2
build.rs, 1
Byte order, 1
bytemuck, 1
byteorder, 1, 2, 3, 4
bytes, 1, 2
Caching, 1, 2
Calendar, 1
candle, 1
candle-core, 1
Capture of variables, 1
cargo, 1, 2, 3
Cargo plugins, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16
cargo plugins, 1
cargo-audit, 1
cargo-auditable, 1, 2, 3, 4
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
cargo-generate, 1, 2
cargo-hack, 1, 2, 3, 4
cargo-hakari, 1, 2, 3
cargo-husky, 1, 2, 3, 4, 5
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-release, 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
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
cdylib, 1
chacha20poly1305, 1
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
clap, 1, 2, 3, 4, 5, 6
clap::Arg::long, 1
clap::Arg::short, 1
clippy, 1
Clone-on-write, 1
Closures, 1
Cloud, 1
Code example, 1
Coercion, 1
color-eyre, 1
Command line utilities, 1, 2, 3, 4, 5, 6, 7, 8, 9
Command runner, 1
Command-line interface, 1, 2, 3, 4, 5, 6, 7, 8, 9
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
Concurrent associative array, 1
Concurrent contexts, 1
config, 1, 2, 3
Configuration, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11
Configuration management, 1
confy, 1
const, 1
Consumer, 1
Content length, 1
Content type, 1
Continuous integration worflow, 1
Control flow, 1
Cooperative multitasking, 1
cornucopia, 1, 2
cos, 1
cosmic-text, 1
Cow, 1
CPU, 1
CPU bound, 1
CPU cores, 1
CPU-heavy computations, 1
Crate, 1, 2
Crate root file, 1
crates.io, 1, 2, 3
crates_io_api, 1, 2
Cross-interpretation, 1
Cross-platform, 1
Cross-platform development, 1
crossbeam, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13
crossbeam spawn, 1
crossbeam-channel, 1, 2, 3, 4, 5
crossbeam-queue, 1, 2, 3
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, 2
crossbeam_channel::bounded, 1
crossbeam_channel::Receiver::iter, 1
crossbeam_channel::Sender::send, 1
crossterm, 1
crux, 1
crux_core, 1
Cryptography, 1, 2, 3, 4, 5, 6, 7
CSV, 1
csv, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12
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
Custom deserializer, 1
Custom event formatter, 1
Custom logger, 1
Custom logger configuration, 1
Dangling references, 1
dapr, 1
Dashboard, 1
dashmap, 1, 2, 3, 4, 5, 6
Data encoding, 1
Data engineering, 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
data-encoding, 1, 2, 3, 4, 5
Database, 1, 2, 3
Database implementations, 1
Database interfaces, 1, 2, 3
Database management systems, 1
Databases, 1, 2, 3, 4, 5, 6, 7, 8
Dataframes, 1
datafusion, 1, 2, 3
Date, 1, 2
date, 1
Date and time, 1, 2, 3, 4, 5, 6, 7
DateTime, 1
Daytona, 1
deadpool, 1
Debug message, 1
Debug output, 1
Debugging, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13
Debugging information, 1
Decoding, 1
Dedicated thread, 1, 2, 3
Default implementation, 1
Delimiter, 1
der, 1
Dereference operator, 1
Derivable traits, 1
derive, 1, 2
derive_more, 1, 2, 3, 4, 5, 6
Deserialization, 1
desktop-lite, 1
Destructuring, 1
Detect loops for a given path, 1
Dev Container, 1, 2
Dev Containers, 1, 2
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
Development tools::Build Utils, 1
Development tools::Debugging, 1
devx, 1
diesel, 1, 2
digest, 1, 2, 3
digest::Context, 1
digest::Digest, 1
directories, 1
dirs, 1
Distributions, 1
Diverging functions, 1
docker, 1, 2, 3, 4
docker compose, 1, 2
docker-cache, 1
Dockerfile, 1
docs.rs, 1, 2
Documentation, 1
Documentation comments, 1
dotenv, 1, 2, 3
dotenvy, 1, 2, 3
druid, 1, 2
dsa, 1
duct, 1, 2, 3
Duplicate filenames, 1
Duration, 1
dyn, 1
dyn-clone, 1, 2, 3, 4
ecdsa, 1
ed25519, 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
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
Enums, 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 variables, 1, 2
envy, 1, 2, 3
Error handling, 1
error_chain, 1, 2, 3, 4, 5, 6, 7, 8
error_chain::ChainedError::backtrace, 1
error_chain::error_chain, 1
error_chain::example_generated::ErrorKind, 1, 2
Event sourcing, 1
Event-driven, 1
exa, 1, 2, 3
Example code, 1
Executables, 1
External command, 1, 2
External FFI bindings, 1
eyre, 1, 2, 3
f32, 1
f64, 1
fancy_regex, 1
FFI, 1
Fields, 1
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, 19
Finance, 1
Firefox, 1
Flag, 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
form-urlencoded, 1
form_urlencoded::byte_serialize, 1
form_urlencoded::parse, 1
Formal methods, 1
Framework, 1
FromStr, 1
Functions, 1
Futures, 1, 2, 3
futures, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
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 collection, 1
Garbage collector, 1
Generics, 1, 2
Git, 1
Github actions, 1
GitHub API, 1
GitHub API - Rate limiting, 1
glidesort, 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
gping, 1, 2
Grapheme, 1
Graphemes, 1
Graphics, 1
gRPC, 1
GUI, 1, 2
GUI manager, 1
Hardware, 1
Hardware support, 1, 2
Hashing, 1, 2
Hashmap, 1
Hashtags, 1
Header, 1
Heap, 1
Heap allocations, 1
helix, 1
Hexadecimal representation, 1
Hour/minute/second, 1
href, 1
HTML document, 1
http, 1, 2, 3, 4
HTTP authentication, 1
HTTP client, 1, 2, 3, 4, 5, 6, 7, 8
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, 18
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
hyperswitch, 1
i128, 1
i16, 1
i32, 1
i64, 1
i8, 1
iced, 1, 2
if let, 1
ignore, 1, 2
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
In-memory, 1
indexmap, 1
indicatif, 1, 2, 3, 4
infisearch, 1, 2, 3
Inner value, 1
inquire, 1
install-action, 1
Interfaces, 1
Interior mutability, 1
Intermediate representation, 1
Internationalization (i18n), 1
Interpreters, 1, 2
Invalid data, 1
isize, 1, 2
ISO 8601, 1
Iterators, 1
iterators, 1
itertools, 1, 2, 3, 4, 5
JetBrains, 1
Join, 1
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 crates, 1
Keys, 1
L1 norm, 1, 2, 3
L2 norm, 1, 2, 3
lapce, 1, 2
Lazy static, 1
lazy_static, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12
lemmy, 1
len, 1
lens, 1
lens_rs, 1, 2
leptos, 1, 2, 3
lexopt, 1
lib.rs, 1, 2, 3, 4
libc, 1
Library crate, 1
Lifetimes, 1, 2
Line editing, 1
Linear-algebra, 1
linfa, 1, 2, 3, 4, 5
Links, 1
Lint checks, 1
Little endian, 1
loco_rs, 1, 2
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
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
Logging, 1
Logging utilities, 1
Logical cpu cores, 1
lru, 1, 2, 3
lsd, 1, 2, 3
Machine learning, 1
Machine-learning, 1, 2
Macros, 1, 2
main, 1, 2
make, 1
mapv, 1
match, 1, 2
matching, 1
Mathematics, 1, 2, 3, 4, 5
md-5, 1
Mdbook, 1
mdbook, 1, 2, 3, 4, 5, 6, 7, 8
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, 3
mdbook-yapp, 1
Meetups, 1
meilisearch, 1
memmap2, 1, 2, 3
memmap2::Mmap::map, 1
Memory leaks, 1
Memory management, 1, 2, 3, 4, 5
Memory map, 1
Memory mapping, 1
Message passing, 1, 2, 3
Message-based, 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
MSSQL, 1
Multi-consumer channels, 1
multimap, 1
Multiple owners, 1, 2
Multithreaded runtimes, 1
Multithreading, 1
Mutable references, 1
Mutex, 1
MySQL, 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
Networking applications, 1
Newsletter, 1
Newtype pattern, 1
No dynamic allocation, 1, 2, 3
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
No std, 1
nom, 1, 2
Non-blocking I/O operations, 1
Non-sequential reads, 1
noplayground, 1
notify, 1, 2, 3
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
nvm, 1
OAuth, 1
Object-safe traits, 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
Optimization, 1
Options, 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
Package, 1
pants, 1
Parallel execution, 1
Parallel pipeline, 1
Parallel programming, 1, 2
Parallel sort, 1
Parallel tasks, 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
Parsing, 1
Parsing tools, 1, 2
paste, 1, 2, 3, 4
Pattern, 1
Pattern matching, 1
PBKDF2, 1
pem-rfc7468, 1
percent_encoding, 1, 2, 3
percent_encoding::percent_decode, 1
percent_encoding::utf8_percent_encode, 1
pest, 1, 2
Phone numbers, 1
pico-args, 1
Piped external commands, 1
pkcs8, 1
plotly, 1, 2
plotly.rs, 1
Podcasts, 1
polars, 1, 2
Pool, 1
postage, 1, 2, 3, 4, 5
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
postsack, 1
powi, 1
Pre-release, 1
Preprocessor, 1
Pretty print, 1
Primitives, 1
Private by default, 1
Private items, 1
proc-macro2, 1, 2
Procedural macro helpers, 1
Process, 1
Producer, 1
Production-ready code, 1
Profiling, 1
Progress bars and spinners, 1
Promises, 1
pub, 1
Pull request, 1
pyenv, 1
pyo3, 1, 2, 3
pyOxidizer, 1
pyoxidizer, 1
quickinstall, 1
quote, 1, 2
r3bl_tuify, 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 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
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
Read/write lock, 1
redis, 1, 2
redox, 1
Reference counting, 1
References, 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
release-plz, 1
Rendering, 1, 2
Rendering engine, 1
Replace, 1
Replacement string syntax, 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
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, 6, 7
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
Robotics, 1
rocket, 1, 2
roogle, 1, 2
rsa, 1
ruff, 1, 2
rug, 1
rui, 1, 2
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 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
Rust patterns} ```rust {{#include ../../../deps/tests/cats/rust_patterns/unwrap_or_else.rs:example, 1
Rust playground, 1, 2, 3
Rust starter pack, 1
Rust tooling, 1, 2
Rust tools, 1
rust-cache, 1, 2
rust_cache, 1
rust_decimal, 1
rust_gpu, 1
RUST_LOG, 1, 2, 3, 4
rustc, 1, 2, 3
rustdesk, 1, 2, 3
rustdoc, 1, 2
rustfix, 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
RustPython, 1
rustpython, 1
rustquant, 1, 2, 3, 4, 5, 6
rustup, 1, 2
SaaS, 1
salsa, 1
Salted passwords, 1
same-file, 1, 2
same_file::Handle, 1
same_file::is_same_file, 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, 24, 25, 26, 27
science, 1
Scope, 1
Scripting in Rust, 1
scrypt, 1
sea-orm, 1, 2, 3
seaography, 1, 2, 3
Searching, 1
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, 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
Serialize custom structs, 1
Serverless, 1
SHA-256, 1
sha1, 1
sha2, 1
SHA256, 1
Shadowing, 1
Shared memory concurrency, 1
Shared state, 1
shields.io, 1
short-lived thread, 1
shuttle.rs, 1, 2
Signature, 1
Simulation, 1
Simultaneous references, 1
sin, 1
Single producer, single consumer, 1
Single-threaded runtime, 1
Skipping dotfiles, 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
smartcore, 1, 2
smol, 1, 2, 3, 4, 5, 6
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
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
staticlib, 1
Statistical, 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
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, 4
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
Structs, 1, 2
Submodules, 1
subtle, 1
SUMMARY.md, 1
swc, 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
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
tauri, 1, 2, 3
tempfile, 1, 2, 3, 4
tempfile::Builder, 1
tempfile::Builder::tempdir, 1
Template engine, 1, 2, 3
Template engines, 1, 2
Tensor, 1
tera, 1
termbook, 1, 2
Terminal, 1
Testing, 1, 2
Text, 1, 2
Text editors, 1, 2
Text processing, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15
thiserror, 1, 2, 3, 4, 5, 6
Thread pools, 1, 2, 3, 4
threadpool, 1, 2, 3, 4, 5, 6
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
tokio, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15
Tokio examples, 1
tokio-graceful-shutdown, 1, 2
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, 3
TOML, 1
toml, 1, 2, 3, 4, 5, 6
tonic, 1, 2, 3, 4, 5, 6
tower, 1, 2, 3, 4, 5, 6
tower-http, 1, 2, 3
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
tui, 1, 2, 3
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
User interface, 1
usize, 1, 2
UTC, 1
UTF-8, 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
Visualization, 1
VS code, 1, 2, 3
Vtable, 1
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
WASM, 1
wasmtime, 1
watchmaker, 1, 2, 3
watt, 1, 2, 3
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, 26, 27, 28
WebAssembly, 1, 2, 3, 4
WebSocket, 1
wgpu, 1
while let, 1
windows, 1, 2
Windows APIs, 1
Word, 1
Work-stealing runtime, 1
wrapping_add, 1
x509-cert, 1
xilem, 1, 2, 3, 4, 5
xshell, 1, 2, 3, 4, 5
xsv, 1, 2, 3
year/month/day/weekday, 1
yew, 1, 2, 3, 4
zed, 1, 2, 3, 4
zenoh, 1, 2
zerocopy, 1
zeroize, 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.
It is also inspired from online websites, blogs, and documentation, including:
- The Rust Programming Language⮳
- Rust By Example⮳
- Rust by Practice⮳
- Asynchronous Programming in Rust⮳
- The rustdoc book⮳
docs.rs
⮳- lpalmieri.com⮳
- Codevoweb.com⮳
- Rust for professionals⮳
- Rust Cookbook (fork)⮳
- Rust Design Patterns⮳
- Easy Rust⮳
- The Rustonomicon⮳
This site is not affiliated with the Rust Foundation⮳.