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

Algorithms

Randomness

Sorting

API Bindings

Python Interop

RecipeCratesCategories
Python Interoppyo3cat-api-bindings

Asynchronous

Async

Async and Blocking

Async Channels

Async Traits

RecipeCratesCategories
Async traitscat-asynchronous

Futures

Streams

RecipeCratesCategories
Streamsfuturescat-asynchronous

Tokio

RecipeCratesCategories
Basicstokiocat-asynchronous
Jointokiocat-asynchronous
Spawningtokiocat-asynchronous
IOtokiocat-asynchronous
Graceful shutdowntokio_graceful_shutdowncat-asynchronous

Authentication

Basic Authentication

RecipeCratesCategories
Basic Authenticationreqwestcat-authentication cat-network-programming

Caching

LRU Caching

RecipeCratesCategories
LRU cachelrucat-caching

Command Line Interface

ANSI Terminal

Arguments

TUI

RecipeCratesCategories
Build complex TUIratatuicat-command-line-interface{{hi:Command-line interface}}

User Interaction

Command Line Utilities

Filesystem

RecipeCratesCategories
lsdlsdcat-command-line-utilities
exaexacat-command-line-utilities
brootbrootcat-command-line-utilities
batbatcat-command-line-utilities
openopencat-command-line-utilities

Networking

RecipeCratesCategories
Networking tools: gpinggpingcat-command-line-utilities

Shells

RecipeCratesCategories
Shells and related utilitiesstarshipcat-command-line-utilities

Compilers

Cross Compilation

Faster Linking

Compilation Duration Reduction

Compression

tar

Computer Vision

OpenCV

RecipeCratesCategories
Analyze images with Open CVopencvcat-computer-vision

Shared State

Concurrent Data Structures

Shared State

RecipeCratesCategories
Mutexescat-concurrency
Parking Lotcat-concurrency
Atomicscat-concurrency
arc-swapcat-concurrency

Config

Configuration

Environment Variables

Cryptography

Encryption

RecipeCratesCategories
Salt and hash a password with PBKDF2ring data-encodingcat-cryptography

Hashing

Password Hashing

RecipeCratesCategories
argon2cat-cryptography
scryptcat-cryptography
bcryptcat-cryptography

Data Structures

Bitfield

Maps

RecipeCratesCategories
Insertion-ordered mapindexmapcat-data-structures
Multimapmultimapcat-data-structures
Slotmapslotmapcat-data-structures

Stack-allocated Arrays

RecipeCratesCategories
arrayvecarrayveccat-data-structures
smallvecsmallveccat-data-structures
tinyvectinyveccat-data-structures

UUIDs

RecipeCratesCategories
Generate and parse UUIDsuuidcat-data-structures

Database Implementations

Databases

RecipeCratesCategories
Sledsledcat-algorithms cat-caching cat-concurrency cat-data-structures cat-database-implementations
SurrealDBsurrealdbcat-data-structures [cat-database-implementations] cat-database-implementations cat-embedded
RecipeCratesCategories
Meilisearchmeilisearchcat-database-implementations
Tantivytantivycat-data-structures cat-database-implementations

Date and Time

Duration

Parse

Time

RecipeCratesCategories
Use the time cratetimecat-date-and-time cat-value-formatting cat-parser-implementations cat-no-std

Cargo & Friends

Cargo

Crate Registries

RecipeCratesCategories
Crate Registriescat-development-tools

Package Layout

RecipeCratesCategories
Package layout

Documentation

Badges

RecipeCratesCategories
BadgesShield.iocat-development-tools

Documentation

mdbook

Formatting

Formatting

Installation

Installation

RecipeCratesCategories
Rustupcat-development-tools
Cargo installcat-development-tools
Cargo binstallcat-development-tools

rustup

RecipeCratesCategories
Key commandscat-development-tools

Other

just

miri

RecipeCratesCategories
Miricat-development-tools
Miri installationcat-development-tools

Other

Versioning

Versioning

Development Tools: Build Utils

Build Utils

Development Tools: Cargo-Plugins

Auditing

Building

Code Formatting and Linting

Cross-Compiling

Maintaining

Performance

Watching For Changes

RecipeCratesCategories
Cargo watchcat-development-tools::cargo-plugins
Cargo limitcat-development-tools::cargo-plugins

Writing

Development Tools: Debugging

Alternatives

Config Log

Diagnostic Functions

Log

Tracing

Development Tools: FFI

Generate FFI Bindings

RecipeCratesCategories
bindgenbindgencat-development-tools::ffi cat-external-ffi-bindings
cbindgencbindgencat-development-tools::ffi cat-external-ffi-bindings
cxxcxxcat-development-tools::ffi cat-no-std{{hi:No standard library}}

Development Tools: Procedural Macro-Helpers

Compile Macros

RecipeCratesCategories
Wattwattcat-development-tools::procedural-macro-helpers

Tools

RecipeCratesCategories
cargo expandcat-development-tools::procedural-macro-helpers

Write Proc Macros

Development Tools: Profiling

Assembly

RecipeCratesCategories
Inspect the generated assemblycat-development-tools::profiling

Benchmarking

RecipeCratesCategories
cargo flamegraphcat-development-tools::profiling
Criterioncat-development-tools::profiling
Divancat-development-tools::profiling
Hyperfinecat-development-tools::profiling

Memory

RecipeCratesCategories
Profile heap memorycat-development-tools::profiling

Development Tools: Testing

Fuzzing

RecipeCratesCategories
aflaflcat-development-tools::testing

Testing

Email

Send Emails

RecipeCratesCategories
Send an emaillettrecat-email

Embedded

Embassy

RecipeCratesCategories
Embassyembassycat-embedded

Emulators

Emulators

RecipeCratesCategories
emulatorscat-emulators

Encoding

Complex

CSV

Serde

RecipeCratesCategories
JSONcat-encoding
serde-jsoncat-encoding
monostatecat-encoding
serde-ignoredcat-encoding

Strings

Typecasts

RecipeCratesCategories
bytemuckbytemuckcat-encoding
zerocopyzerocopycat-encoding

External FFI Bindings

External FFI Bindings

RecipeCratesCategories
fficat-external-ffi-bindings

Filesystem

cwd

RecipeCratesCategories
Get the current working directorycat-filesystem

dir

File Watching

Ignore

Read & Write Files

Tempfiles and Directories

User Directories

Finance

Quant

RecipeCratesCategories
RustQuantrustquantcat-finance

Game Development

Game Development

RecipeCratesCategories
Game developmentcat-game-development

Game Engines

Game Engines

RecipeCratesCategories
bevybevy-githubcat-game-engines
fyroxcat-game-engines
ggezcat-game-engines
macroquadcat-game-engines
glamcat-game-engines

Graphics

Webgpu

RecipeCratesCategories
wgpuwgpucat-graphics

GUI

2D Renderers

RecipeCratesCategories
femtovgcat-gui
skia-safecat-gui
vellocat-gui
vgercat-gui
webrendercat-gui

Clipboard

RecipeCratesCategories
arboardcat-gui

File Dialogs

RecipeCratesCategories
rfdcat-gui

GTK

RecipeCratesCategories
gtk4cat-gui
relm4cat-gui

Immediate Mode Gui

RecipeCratesCategories
eGUIeguicat-gui

Other GUI

RecipeCratesCategories
xilemcat-gui
slintcat-gui
GPUIcat-gui
druidcat-gui

Retained-mode GUI

RecipeCratesCategories
icedcat-gui
floemcat-gui

Text Layout

RecipeCratesCategories
cosmic-textcat-gui
parleycat-gui

UI Layout

RecipeCratesCategories
taffycat-gui
morphormcat-gui

Web-based GUI

RecipeCratesCategories
Tauritauricat-gui
dioxuscat-gui

Window Creation

RecipeCratesCategories
winitcat-gui
taocat-gui

Hardware Support

Processor

RecipeCratesCategories
Get the number of logical cpu coresnum_cpuscat-hardware-support

Internationalization

Internationalization

RecipeCratesCategories
internationalizationcat-internationalization

Localization

Localization

RecipeCratesCategories
Localizationcat-localization

Mathematics

Additional Numeric Types

Complex Numbers

Linear Algebra

Statistics

Trigonometry

Memory Management

Global Static

RecipeCratesCategories
Declare lazily evaluated constantlazy_staticcat-caching cat-rust-patterns cat-memory-management

Lazy Initialization

RecipeCratesCategories
stdcat-memory-management
once_cellonce_cellcat-memory-management
lazy_staticlazy_staticcat-memory-management

Multimedia Audio

Audio

RecipeCratesCategories
Audiocat-multimedia::audio

Multimedia Encoding

Encoding

RecipeCratesCategories
Multimedia encodingcat-multimedia::encoding

Multimedia Images

Images

RecipeCratesCategories
Imagescat-multimedia::images

Multimedia Video

Video

RecipeCratesCategories
Videocat-multimedia::video

Network Programming

Reverse Proxy

RecipeCratesCategories
Ratholeratholecat-network-programming
ngrokngrokcat-network-programming
nginxcat-network-programming
Pingorapingoracat-network-programming

Server

No alloc

No alloc

RecipeCratesCategories
No alloccat-no-std::no-alloc

OS: FreeBSD APIs

FreeBSD

RecipeCratesCategories
FreeBSDcat-os::freebsd-apis

OS: Linux APIs

Linux

RecipeCratesCategories
Linuxcat-os::linux-apis

OS: macOS APIs

macOS

RecipeCratesCategories
macOScat-os::macos-apis

OS: Unix APIs

Unix

RecipeCratesCategories
rustixrustixcat-os::unix-apis{{hi:Unix APIs}}
nixnixcat-os::unix-apis{{hi:Unix APIs}}

OS: Windows APIs

Windows

RecipeCratesCategories
Integrate with Windows APIswindows-githubcat-os::windows-apis
winapiwinapicat-os::windows-apis

Parser Implementations

Parser Implementations

RecipeCratesCategories
JavaScriptcat-parser-implementations

Parsing

Parsing

RecipeCratesCategories
Nomnomcat-parsing
Pestpest-websitecat-parsing
Tree sittertree-sittercat-parsing

Rendering: Data Formats

Data Formats

RecipeCratesCategories
Data formatscat-rendering::data-formats

Rendering Engine

Rendering Engines

RecipeCratesCategories
Rendercat-rendering::engine

Rendering: Graphics APIs

Graphics APIs

RecipeCratesCategories
Graphicscat-rendering::graphics-api

Rust Patterns

Design Patterns

RecipeCratesCategories
Abstract factorycat-rust-patterns
dyn-clonecat-rust-patterns
Lenscat-rust-patterns

Error Customization

RecipeCratesCategories
Anyhowcat-rust-patterns
thisErrorcat-rust-patterns
miettecat-rust-patterns
color-eyrecat-rust-patterns

Error Handling

Functional Programming

RecipeCratesCategories
Compose iterators with itertoolscat-rust-patterns

Rust Idioms

RecipeCratesCategories
Rust idiomscat-rust-patterns

Science Geo

Geo

RecipeCratesCategories
Geosciencecat-science::geo

Science Neuroscience

Neuro

RecipeCratesCategories
Neurosciencecat-science::neuroscience

Science Robotics

Robotics

RecipeCratesCategories
Roboticsrobotics.rscat-science::robotics

Useful Robotics Tools And Libs

RecipeCratesCategories
Open CV
zenohcat-science::robotics
Open Rust Roboticscat-science::robotics
bonsai-btcat-science::robotics

Simulation

Simulation

RecipeCratesCategories
Simulationcat-simulation

Template Engine

Tera

RecipeCratesCategories
Create HTML files from a templateteracat-template-engine

tinytemplate

RecipeCratesCategories
tinytemplatecat-template-engine

Text Editors

IDEs

Text Processing

Regex

RecipeCratesCategories
Longer regex example

String Concat

RecipeCratesCategories
Benchmark

String Parsing

Value Formatting

Value Formatting

RecipeCratesCategories
Value formattingcat-value-formatting

Virtualization

Virtualization

RecipeCratesCategories
Virtualizationcat-virtualization

Visualization

Visualization

RecipeCratesCategories
Visualizationcat-visualization

Wasm

Others

RecipeCratesCategories
wasmer
wasmtime

Yew

RecipeCratesCategories
Yewyewcat-wasm

Web Programming: HTTP Client

APIs

Download

HTTP Clients

RecipeCratesCategories
reqwestreqwestcat-web-programming::http-client
urequreqcat-web-programming::http-client
hyperhypercat-web-programming::http-client

Requests

Web Pr: HTTP Server

actix

RecipeCratesCategories
Actixcat-web-programming cat-web-programming::http-server

axum

RecipeCratesCategories
Axumcat-web-programming cat-web-programming::http-server

Batteries-included frameworks

RecipeCratesCategories
Loco
Rust on Nails

CORS

RecipeCratesCategories
CORScat-web-programming

GraphQL

RecipeCratesCategories
async-graphql

gRPC

RecipeCratesCategories
tonic

hyper

RecipeCratesCategories
hyper

Middleware

RecipeCratesCategories
Towertowercat-web-programming cat-web-programming::http-server
Tower HTTPtower_httpcat-web-programming cat-web-programming::http-server
Alternatives

Other Frameworks

RecipeCratesCategories
Rocketcat-web-programming cat-web-programming::http-server
Leptoscat-web-programming::http-server

Static Website Generators

Web-Programming Websocket

Websocket

RecipeCratesCategories
Low-levelcat-web-programming::websocket
General Purposecat-web-programming::websocket

Contributing

API Documentation

Development Editing

Section
Book editing

Dev Container Docker

Dev Environment Setup

Optional Preprocessors

Publication

Section
Publication

Repo Structure

Topics of Interest

Language

Attributes

Closures

Control Flow

Enums

Section
Enums

Functions

Generics

Iterators

Section
Iterators

Lifetimes

Macros

Section
Macros

Main

Match

Modules

Ownership Borrowing

Rust Install

Simple Data Types

Slices

Section
Slices

Structs

Section
Structs

Traits

Trait Objects

Section
trait-objects

Variables and Constants

Blogs, Podcasts, Meetups

Books

Section
Books

Companies

Example Code

Recipe
example-code

Learning

Section
Rust Learning
Section
Rust Links

Rust Cheatsheets

Architecture

Architecture

RecipeCratesCategories
Architecture

Cloud

AWS

Rust-native Cloud Development

RecipeCratesCategories
Shuttle Cloud Platform

Serverless

Containers

Containers

RecipeCratesCategories
Docker
Docker Compose

Cross Platform

Crux

RecipeCratesCategories
Develop across platforms with Cruxcrux_core

Data Processing

CSV

Dataframes

RecipeCratesCategories
Manipulate data in a tabular formatpolars

Data Engineering

Data Visualization

RecipeCratesCategories
Plot and graph data

Devops

CD/CI

RecipeCratesCategories
CD / CI

Dependency Management

RecipeCratesCategories
deps.rs
Rust Digger

DevOps

RecipeCratesCategories
Daytona

Github Actions

Git Hooks

Release Automation

RecipeCratesCategories
cargo release
release-plz

GPU

GPU

RecipeCratesCategories
rust-gpurust_gpu-github

Scripting

rhai

Written in Rust

Development Tools

RecipeCratesCategories
Compilers
swc
Code editors
lapce
zed
Build tools

Others

Python Tools

RecipeCratesCategories
rustpythoncat-api-bindings
pyOxidizercat-api-bindings
Ruff
uv

Standard Library

Asref

RecipeCrates
AsRefstd

Cow

Derive

Hashmaps

RecipeCrates
Hashmapsstd

Option

Result

RecipeCrates
Resultstd

Smart Pointers

RecipeCrates
Boxstd
Rcstd
RefCellstd

Strings

Vectors

RecipeCrates
Vecstd

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.rsstatistics⮳. 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

rust-howto-todo-github

rust-howto-contributing

rust-howto-drafts

rust-howto-github

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

  • 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 in book/html.
  • The intermediate (processed) Markdown is in book/markdown. The mdbook⮳ configuration is in book.toml; the templates and assets are in theme and static respectively.
  • The Rust code is organized as a cargo⮳ workspace:
    • Examples that are embedded in the book are found in deps/tests and deps/examples. These are mostly single, short .rs files. The deps/Cargo.toml list all dependencies used by the embedded examples. Use cargo add <crate> -F <feature> while in the deps 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.
  • 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

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.

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 or cargo +nightly fmt --all), check it compiles (just buildall or cargo build --all-targets), lint it (just clippyall or cargo clippy --all-targets), and test it (just testall or cargo test --test <name> for an individual example). You may also cargo run --example <name>.
    • Include your code example in the Markdown via {{# include /path/to/file.rs}} within pairs of triple backticks.
  • 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 the mdbook-runnable attribute forces the play button to be displayed when ignore⮳ 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. Use cargo new or cargo 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

  • mdbook-linkcheck⮳ is a backend for mdbook⮳ that will check links. Install with cargo install mdbook-linkcheck. Uncomment the related section in book.toml.
  • mdbook-hide⮳ hides chapters under construction. Install with cargo install mdbook-hide. Uncomment the related section in book.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-->
cargo install mdbook_keeper --git https://github.com/tfpk/mdbook_keeper.git

Generate the docs.rs Documentation

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 select Download... or use VS Code's built-in Simple Browser command.
  • Or install the Live Server or MS Live 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 in Account 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

Install Rust

Key Steps

On WSL / Unix:

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
  • Check whether you have Rust installed correctly
rustc --version
cargo --version
  • Open the documentation, if needed
rustup doc
  • Create a new project
cargo new hello_world
cd hello_world
code . # open VS Code (or your favorite editor) and edit the code as you wish
  • Build / run the code.
cargo check # check if the code can compile
cargo build # compile
cargo run # run the executable

cargo run builds the code if cargo build has not been invoked before or the code has changed.

Main function

use std::fs::File;
use std::io::Read;

use anyhow::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

Rust by example - Primitives

  • Integers: i8⮳, i16⮳, i32⮳, i64⮳, i128⮳, isize
  • Unsigned: u8⮳, u16⮳, u32⮳, u128⮳, usize
    • usize⮳ and isize⮳ are 32 or 64 bits, depending on the architecture of the computer.
  • 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;
  • Arrays: let a: [i32; 5] = [1, 2, 3, 4, 5]; allocated on the stack. access via let 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 as wrapping_add⮳.
  • Return the std::option::Option::None⮳ value if there is overflow with the checked_* 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

Rust by example - Variable bindings Rust by example - 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

Rust by example - 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.

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

Rust by example - 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

Rust by example - Control flow

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

Rust by example - 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

Rust by example - 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

Traits (blog)

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

Trait Objects (docs)

Attributes

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

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

Attributes reference

Rust by example - attributes

Generics

Rust by example - Generics

Generic Structs


use std::fmt::Display;

struct Point<T> {
    x: T,
    y: T,
}

impl<T> Point<T> {
    fn x(&self) -> &T {
        &self.x
    }
}

impl Point<f32> {
    // specify constraints on generic types
    fn distance_from_origin(&self) -> f32 {
        (self.x.powi(2) + self.y.powi(2)).sqrt()
    }
}

impl<T: Display + PartialOrd> Point<T> {
    // use Trait Bounds to Conditionally Implement Methods
    fn cmp_display(&self) {
        if self.x >= self.y {
            println!("The largest member is x = {}", self.x);
        } else {
            println!("The largest member is y = {}", self.y);
        }
    }
}

fn main() {
    let p = Point { x: 5, y: 10 };

    println!("p.x = {}", p.x());
}

Lifetimes

Rust by example - Lifetimes

Prevent dangling references.

&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

Rust by example - Modules

Crates can contain modules.

Declaring modules: In the crate root file (main.rs or lib.rs), you can declare new modules; say, you declare a “garden” module with mod garden; (or pub mod garden; for public); The compiler will look for the module’s code in these places:

  • Inline, within curly brackets that replace the semicolon following mod garden
  • In the file src/garden.rs
  • In the file src/garden/mod.rs (older style)

In any file other than the crate root, you can declare submodules. For example, you might declare mod vegetables; in ``src/garden.rs`. The compiler will look for the submodule’s code within the directory named for the parent module in these places:

  • Inline, directly following mod vegetables, within curly brackets instead of the semicolon
  • In the file src/garden/vegetables.rs
  • In the file src/garden/vegetables/mod.rs (older style)

In Rust, all items (functions, methods, structs, enums, modules, and constants) are private to parent modules by default. Items can access other items in the same module, even when private.

Items in a parent module can’t use the private items inside child modules, but items in child modules can use the items in their ancestor modules.

book-rust-by-example-visibility-rules

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.

book-rust-by-example-use


// 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

Rust by example - match


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

Pattern matching

Closures

Closures

Rust by example - 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

Iterators

Macros

Section
Macros

Rust reference - Macros

Rust by example - MacrosRust by example - 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

Result

RecipeCrates
Resultstd

Vectors

RecipeCrates
Vecstd

HashMap

RecipeCrates
Hashmapsstd

Strings

Copy-on-write

Smart Pointers

RecipeCrates
Boxstd
Rcstd
RefCellstd

Automatic Trait Derivation

Asref

RecipeCrates
AsRefstd

Option

Option

std

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

Extracting the value contained in Option

std]c-std

These methods extract the contained value in an std::option::Option when it is the Some variant. If the std::option::Option⮳ is None:

Combinators

std]c-std


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

RecipeCrates
Resultstd

Result

See also

Vectors

RecipeCrates
Vecstd

Vec

std

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

RecipeCrates
Hashmapsstd

std

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

String type

std

#![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

std]c-std

#![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

std]c-std

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

std

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

std

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

RecipeCrates
Boxstd
Rcstd
RefCellstd
  • Rc<T> enables multiple owners of the same data; Box<T> and RefCell<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 the RefCell<T> even when the RefCell<T> is immutable.

Box

book-rust-box std

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

std

The Rc<T> type keeps track of the number of references to data on the heap so that data can have multiple owners.

RefCell

std

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

RecipeCrates
AsRefstd

When and why to use AsRefinstead of &T

Automatic trait derivation

std

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.

Derivable traits

// 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

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:

or older resources, such as:

Best of Rust crates

Crates

Crates mentioned in this book, by alphabetic order.

A

actix-web anyhow approx arrow async-channel async-std async-stream async-trait axum

B

bacon base64 bat bevy bitflags bonsai-bt broot byteorder bytes

C

candle_core cargo cargo-auditable cargo-cache cargo-crates cargo-edit cargo-expand cargo-generate cargo-hack cargo-hakari cargo-husky cargo-limit cargo-machete cargo-make cargo-nextest cargo-tarpaulin cargo-udeps cargo-wizard cargo-xtask cc chrono clap concat-string concat_strs config confy cornucopia cosmic-text crates_io_api cross crossbeam crossbeam-channel crossbeam-queue crossbeam-utils crux_core csv

D

dashmap data-encoding datafusion derive_more diesel digest dotenv dotenvy druid duct dyn-clone

E

egui elasticsearch embassy env_logger envy exa eyre

F

flagset flate2 floem form-urlencoded futures futures-executor

G

glidesort glob gping

H

http http-body-util hyper

I

iced image indicatif infisearch itertools

J

just

K

kanal kani

L

lazy_static lens leptos linfa loco_rs log log4rs lru lsd

M

mdbook mdbook-cmdrun mdbook-hide mdbook-journal mdbook-keeper mdbook-linkcheck mdbook-pagetoc mdbook-private mdbook-tera mdbook-theme mdbook-toc meilisearch memmap2 miette mime minisearch mio miri mongodb monostate multimap

N

nalgebra native-windows-gui ndarray nom notify num num_cpus

O

once_cell open opencv openrr

P

parking_lot paste percent-encoding pest plotly polars postage postgres proc-macro2 pyo3 pyoxidizer

Q

quote

R

r3bl_tuify rand rand_distr ratatui rayon redis regex reqwest rhai riker ring roogle ruff rui rusqlite rustdesk rustquant

S

salsa same-file sea-orm seaography select semver serde serde_json sled slint slotmap smol sqlx stakker starship std stork-search swc_ecma_parser syn syslog

T

tantivy tar tauri tempfile termbook thiserror threadpool tinysearch tokio tokio-graceful-shutdown toml tonic tower tower-http tracing tracing-subscriber trillium typesense

U

unicode-segmentation url

W

walkdir wasmtime watchmaker watt wgpu windows

X

xilem xshell xsv

Y

yew

Z

zed zenoh

Crates by category

CategoryDescriptionCrates
cat-api-bindingsIdiomatic wrappers of specific APIs for convenient access from Rust. Includes HTTP API wrappers as well. Non-idiomatic or unsafe bindings can be found in External FFI bindings.elasticsearch flate2 pyo3
cat-accessibilityAssistive technology that helps overcome disabilities and impairments to make software usable by as many people as possible.xilem
cat-algorithmsRust implementations of core algorithms such as hashing, sorting, searching, and more.crossbeam-channel crossbeam-utils dashmap glidesort itertools linfa num rand rand_distr rustquant sled
cat-asynchronousCrates to help you deal with events independently of the main program flow, using techniques like futures, promises, waiting, or eventing.actix-web async-channel async-std async-trait axum futures kanal mio mongodb postage smol stakker tokio tokio-graceful-shutdown tonic tower tower-http tracing tracing-subscriber
cat-development-tools::build-utilsUtilities for build scripts and other build time steps.cargo-make cc xshell
cat-cachingCrates to store the results of previous computations in order to reuse the results.lru sled slotmap
cat-development-tools::cargo-pluginsSubcommands that extend the capabilities of Cargo.cargo-auditable cargo-cache cargo-crates cargo-edit cargo-expand cargo-hack cargo-hakari cargo-make cargo-nextest cargo-udeps cargo-wizard
cat-command-line-utilitiesApplications to run at the command line.bacon bat broot cargo-cache cargo-crates cargo-hack cargo-make exa infisearch just lens lsd mdbook-journal mdbook-tera r3bl_tuify starship
cat-command-line-interfaceCrates to help create command line interfaces, such as argument parsers, line-editing, or output coloring and formatting.clap indicatif r3bl_tuify ratatui
cat-compressionAlgorithms for making data smaller.flate2
cat-concurrencyCrates for implementing concurrent and parallel computation.async-channel async-std crossbeam crossbeam-channel crossbeam-queue crossbeam-utils dashmap kanal parking_lot rayon sled smol stakker threadpool
cat-configCrates to facilitate configuration management for applications.config envy toml
cat-cryptographyAlgorithms intended for securing data.digest ring
cat-data-structuresRust implementations of particular ways of organizing data suited for specific purposes.bytes crossbeam crossbeam-channel crossbeam-queue crossbeam-utils dashmap kanal ndarray num semver sled slotmap stakker tantivy
cat-database-implementationsDatabases allow clients to store and query large amounts of data in an efficient manner. This category is for database management systems implemented in Rust.sled tantivy
cat-databaseCrates to interface with database management systems.cornucopia diesel elasticsearch mongodb postgres rusqlite sea-orm seaography
cat-date-and-timeCrates to manage the inherent complexity of dealing with the fourth dimension.chrono
cat-development-tools::debuggingCrates to help you figure out what is going on with your code such as logging, tracing, or assertions.cargo-expand env_logger log tracing tracing-subscriber
cat-development-toolsCrates that provide developer-facing features such as testing, debugging, linting, performance profiling, autocompletion, formatting, and more.bacon cargo-cache cargo-edit cargo-husky cargo-make cargo-tarpaulin cargo-udeps concat_strs derive_more just kani paste
cat-embeddedCrates that are primarily useful on embedded devices or without an operating system.rhai
cat-encodingEncoding and/or decoding data from one data format to another.base64 byteorder cargo-auditable csv data-encoding image monostate serde serde_json toml url
cat-multimedia::encodingCrates that encode or decode binary data in multimedia formats.image
cat-development-tools::ffiCrates to help you better interface with other languages. This includes binding generators and helpful language constructs.pyo3
cat-filesystemCrates for dealing with files and filesystems.glob notify tempfile walkdir xshell
cat-financeCrates for dealing with money. Accounting, trading, investments, taxes, banking and payment processing using government-backed currencies.rustquant
cat-guiCrates to help you create a graphical user interface.bevy druid egui iced rui slint tauri xilem yew
cat-game-developmentFor crates that focus on some individual part of accelerating the development of games.egui
cat-game-enginesFor crates that try to provide a "one-stop-shop" for all of your game development needs.bevy
cat-graphicsCrates for graphics libraries and applications, including raster and vector graphics primitives such as geometry, curves, and color.bevy xilem
cat-web-programming::http-clientCrates to make HTTP network requests.crates_io_api hyper reqwest
cat-web-programming::http-serverCrates to serve data over HTTP.actix-web axum hyper trillium
cat-hardware-supportCrates to interface with specific CPU or other hardware features.num_cpus
cat-multimedia::imagesCrates that process or build images.image
cat-internationalizationCrates to help develop software capable of adapting to various languages and regions.xilem
cat-mathematicsCrates with a mathematical aspect.linfa nalgebra rustquant
cat-memory-managementCrates to help with allocation, memory mapping, garbage collection, reference counting, or interfaces to foreign memory managers.crossbeam lazy_static once_cell slotmap
cat-network-programmingCrates dealing with higher-level network protocols such as FTP, HTTP, or SSH, or lower-level network protocols such as TCP or UDP.actix-web async-std axum bytes hyper smol tokio tonic tower tower-http zenoh
cat-no-std::no-allocCrates that are able to function without the Rust alloc crate.itertools monostate paste serde
cat-no-stdCrates that are able to function without the Rust standard library.anyhow async-trait bitflags byteorder crossbeam crossbeam-queue crossbeam-utils data-encoding derive_more digest dyn-clone itertools lazy_static lru monostate nalgebra num paste rand rand_distr rhai ring semver serde serde_json slint tracing
cat-osBindings to operating system-specific APIs.threadpool
cat-parser-implementationsParsers implemented for particular formats or languages.csv rhai serde_json syn toml url
cat-parsingCrates to help create parsers of binary and text formats. Format-specific parsers belong in other, more specific categories.byteorder nom pest toml
cat-development-tools::procedural-macro-helpersCrates to help you write procedural macros in Rust.derive_more proc-macro2 quote syn watt
cat-development-tools::profilingCrates to help you figure out the performance of your code.tracing tracing-subscriber
cat-renderingReal-time or offline rendering of 2D or 3D graphics, usually with the help of a graphics card.bevy
cat-rendering::engineHigh-level solutions for rendering on the screen.slint
cat-science::roboticsCrates related to robotics.openrr
cat-rust-patternsShared solutions for particular situations specific to programming in Rust.anyhow concat-string concat_strs derive_more dyn-clone eyre itertools lazy_static miette monostate once_cell thiserror
cat-scienceCrates related to solving problems involving physics, chemistry, biology, machine learning, geoscience, and other scientific fields.linfa nalgebra ndarray num rustquant
cat-simulationCrates used to model or construct models for some activity, e.g. to.simulate a networking protocol.rustquant
cat-template-engineCrates designed to combine templates with data to produce result documents, usually with an emphasis on processing text.mdbook-journal mdbook-tera
cat-development-tools::testingCrates to help you verify the correctness of your code.cargo-hack cargo-make cargo-nextest
cat-text-processingCrates to deal with the complexities of human language when expressed in textual form.regex
cat-web-programmingCrates to create applications for the web.crates_io_api http http-body-util mongodb tauri tonic tower-http trillium url yew
cat-wasmCrates for use when targeting WebAssembly, or for manipulating WebAssembly.nalgebra reqwest rhai stork-search watt yew
cat-web-programming::websocketCrates to communicate over the WebSocket protocol.actix-web
cat-os::windows-apisBindings to Windows-specific APIs.windows

All Valid Categories

Algorithms

cat-algorithms

Rust implementations of core algorithms, such as hashing, sorting, searching, and more.

Random Numbers

Sorting

Generate Random Values

Generate random numbers

rand cat-algorithms cat-no-std

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

rand cat-algorithms cat-no-std

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

rand_distr cat-algorithms cat-no-std

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

rand cat-algorithms cat-no-std

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

rand cat-algorithms

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

rand cat-os

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

Sort a Vector of Integers

std cat-science

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

std cat-science

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

std cat-science

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

RecipeCratesCategories
Architecture

Links

Building a SaaS with Rust and Next.js

Asynchronous programming

cat-asynchronous

Crates to help you deal with events independently of the main program flow, using techniques like futures, promises, waiting, or eventing.

RecipeCratesCategories
Basicstokiocat-asynchronous
Jointokiocat-asynchronous
Spawningtokiocat-asynchronous
IOtokiocat-asynchronous
Graceful shutdowntokio_graceful_shutdowncat-asynchronous
RecipeCratesCategories
Async traitscat-asynchronous
RecipeCratesCategories
Streamsfuturescat-asynchronous

Async

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:

Basic Example

cat-asynchronous

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 the std::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⮳ and async_std. Most async applications, and some async crates, depend on a specific runtime.

Async runtimes

async-std smol embassy mio cat-asynchronous

In most cases, prefer the tokio runtime - see The State of Async Rust: Runtimes⮳.

Alternatives to the Tokio async ecosystem include:

  • async-std async_std-crates.io⮳: async version of the Rust standard library. No longer maintained?
  • smol Smol
  • embassy Embassyembassy-github for embedded systems.
  • mio 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

Are we async yet?

Asynchronous Programming in Rust (book)

Async traits

RecipeCratesCategories
Async traitscat-asynchronous

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.

async-trait async_trait-github cat-asynchronous

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;
}

Tokio

RecipeCratesCategories
Basicstokiocat-asynchronous
Jointokiocat-asynchronous
Spawningtokiocat-asynchronous
IOtokiocat-asynchronous
Graceful shutdowntokio_graceful_shutdowncat-asynchronous

Basics

tokio tokio-crates.io tokio-github tokio-lib.rs cat-asynchronous cat-network-programming

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.

Current thread runtime

equivalent to

fn main() {
    tokio::runtime::Builder::new_current_thread()
        .enable_all()
        .build()
        .unwrap()
        .block_on(async {
            println!("Hello world");
        })
}

LocalSet

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

tokio_graceful_shutdown cat-asynchronous

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())
}

Channels for use in async code

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 cat-asynchronous

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

tokio cat-asynchronous

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

async-channel

postage postage-crates.io postage-github postage-lib.rs

Fast sync and async channel:

kanal kanal-crates.io kanal-github kanal-lib.rs

Streams

RecipeCratesCategories
Streamsfuturescat-asynchronous

futures cat-asynchronous

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

See also Tokio async-stream.

async-stream async_stream-github

Futures crate

futures futures-crates.io cat-asynchronous

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 cat-asynchronous

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

futures cat-asynchronous

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

futures cat-asynchronous

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_executor

Mixing Async and Blocking Code

Calling blocking code from async code

tokio tokio-crates.io tokio-github tokio-lib.rs cat-asynchronous cat-network-programming

  • 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, use rayon⮳, or spawn a dedicated thread.

See Async: What is blocking? blog post⮳.

Tokio spawn_blocking

tokio tokio-crates.io tokio-github tokio-lib.rs cat-asynchronous cat-network-programming

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

rayon rayon-crates.io rayon-github rayon-lib.rs cat-asynchronous cat-concurrency

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

rayon rayon-crates.io rayon-github rayon-lib.rs cat-asynchronous cat-concurrency

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

Bridging with sync codetokio tokio-crates.io tokio-github tokio-lib.rs cat-asynchronous cat-network-programming

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 futures_executor-crates.io futures_executor-github futures_executor-lib.rs cat-asynchronous

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

tokio tokio-crates.io tokio-github tokio-lib.rs cat-asynchronous cat-network-programming

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

RecipeCratesCategories
Basic Authenticationreqwestcat-authentication cat-network-programming

Basic Authentication

RecipeCratesCategories
Basic Authenticationreqwestcat-authentication cat-network-programming

Perform a basic HTTP authentication

reqwest cat-network-programming cat-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

Serverless

Rust-native Clouds

RecipeCratesCategories
Shuttle Cloud Platform

Implement Rust applications on AWS

AWS 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.

AWS Rust SDK

aws-doc-sdk-examples

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

Use Rust for serverless computing

serverless rust

Use the dapr distributed runtime

Dapr⮳ is a portable, event-driven, serverless runtime for building distributed applications across cloud and edge.

Dapr SDK for Rust: dapr-rust-sdk-github

Dapr Rust SDK example: dapr-rust-examples

Rust-native cloud development

RecipeCratesCategories
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

RecipeCratesCategories
Build complex TUIratatuicat-command-line-interface{{hi:Command-line interface}}

See also

Command Line Applications in Rust (book)

Code⮳ for Command-Line Rust (O'Reilly, 2022, ISBN 9781098109417)

Command-line argument parsing

Using clap's builder API

clap clap-examples clap-github cat-command-line-interface

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

clap (tutorial) (cookbook) clap examples cat-command-line-interface

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

lexopt lexopt-crates.io lexopt-github lexopt-lib.rs

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

pico-args pico-args-crates.io pico-args-github pico-args-lib.rs

ANSI Terminal

ansi_term cat-command-line-interface

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

cat-command-line-interface

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

cat-command-line-interface

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

crossterm crossterm-crates.io crossterm-github crossterm-lib.rs

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...)

Terminal User Interfaces

RecipeCratesCategories
Build complex TUIratatuicat-command-line-interface{{hi:Command-line interface}}

Build complex TUI

ratatui ratatui-crates.io ratatui-github ratatui-lib.rs

ratatui⮳ is a lightweight, high-level library that provides a set of widgets, layouts, and utilities to build complex Rust TUIs.

See also

tuitui-crates.iotui-githubtui-lib.rs

A library to build rich terminal user interfaces or dashboards

r3bl_tuify r3bl_tuify-crates.ioblog-tuify

User interaction

Ask for confirmation, selection, text input

inquire inquire-crates.io inquire-github inquire-lib.rs cat-command-line-interface cat-value-formatting

inquire provides several different prompts in order to interactively ask the user for information via the CLI.

Display progress bars and spinners

indicatif indicatif-crates.io indicatif-github indicatif-lib.rs cat-command-line-interface

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

cat-command-line-utilities

RecipeCratesCategories
lsdlsdcat-command-line-utilities
exaexacat-command-line-utilities
brootbrootcat-command-line-utilities
batbatcat-command-line-utilities
openopencat-command-line-utilities
RecipeCratesCategories
Networking tools: gpinggpingcat-command-line-utilities
RecipeCratesCategories
Shells and related utilitiesstarshipcat-command-line-utilities

See also

My terminal became more Rusty Community

File listing and display

RecipeCratesCategories
lsdlsdcat-command-line-utilities
exaexacat-command-line-utilities
brootbrootcat-command-line-utilities
batbatcat-command-line-utilities
openopencat-command-line-utilities

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-github

exa is a modern replacement for ls.

broot

broot broot-github is a new way to see and navigate directory trees.

bat

bat is a fast cat clone with syntax highlighting and Git integration. bat-github

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

open open-crates.io open-github open-lib.rs

Opens a path or URL using the program configured on the system.

Networking

RecipeCratesCategories
Networking tools: gpinggpingcat-command-line-utilities

gping

Ping, but with a graph gping

apt install gping

Shells and related utilities

RecipeCratesCategories
Shells and related utilitiesstarshipcat-command-line-utilities

starship

starship starship-crates.io starship-github starship-lib.rs cat-command-line-utilities

starship-github is a fast, highly customizable prompt for any shell.

Compression

Algorithms for making data smaller.

Working with Tarballs

Decompress a tarball

flate2 tar cat-compression

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

flate2 tar cat-compression

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/logspath 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

flate2 tar cat-compression

Iterate over the tar::Archive::entries⮳. Use std::path::Path::strip_prefix⮳ to remove the specified path prefix (bundle/logs). Finally, extract the tar::Entry⮳ via tar::Entry::unpack⮳.

use std::fs::File;
use std::path::PathBuf;

use anyhow::Result;
use flate2::read::GzDecoder;
use tar::Archive;

pub fn main() -> Result<()> {
    let file = File::open("temp/archive.tar.gz")?;
    let mut archive = Archive::new(GzDecoder::new(file));
    let prefix = "bundle/logs";

    println!("Extracted the following files:");
    archive
        .entries()?
        .filter_map(|e| e.ok())
        .map(|mut entry| -> Result<PathBuf> {
            let path = entry.path()?.strip_prefix(prefix)?.to_owned();
            entry.unpack(&path)?;
            Ok(path)
        })
        .filter_map(|e| e.ok())
        .for_each(|x| println!("> {}", x.display()));

    Ok(())
}

Concurrency

cat-concurrency

This section covers concurrent and parallel programming.

Parallelism

  • True simultaneous execution of multiple tasks on multiple cores or processors.

  • Mechanism: uses operating system threads.

  • Important for CPU-heavy computations.

  • Often requires explicit management of threads and thread pools.

  • Requires careful synchronization to prevent data races (using mechanisms like Mutexes or atomics).

  • Overhead due to thread creation and switching.

Key constructs in Rust:

  • Threads are independent units of execution that can be spawned using e.g. std::thread::spawn.
  • Mutexes e.g. std::sync::Mutex protect shared data from race conditions.
  • Channels e.g. std::sync::mpsc allow threads to communicate and exchange data.

Here are the topics we’ll cover:

See Also

book-rust-concurrency

Send, Sync traits

RecipeCratesCategories
Sendcat-concurrency
Synccat-concurrency
}

Send

Sync

Multithreading

Spawn, join

std cat-concurrency

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

std cat-concurrency

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

rayon rayon-github cat-concurrency

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

cat-concurrency

use rayon::prelude::*;

fn main() {
    let mut v = [-5, 4, 1, -3, 2];
    v.par_sort();
    println!("{:#?}", v);
}

Custom parallel tasks

cat-concurrency

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

threadpool

Threads

Spawn a short-lived thread

crossbeam cat-concurrency

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

crossbeam cat-concurrency

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

crossbeam cat-concurrency

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

lazy_static cat-rust-patterns cat-concurrency

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

threadpool num_cpus walkdir ring cat-concurrencycat-filesystem

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

threadpool num num_cpus image cat-concurrency cat-science cat-rendering

This example generates an image by drawing a fractal from the Julia set⮳ with a thread pool for distributed computation.

julia-set

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(())
}

Parallel Tasks

Mutate the elements of an array in parallel

rayon cat-concurrency

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

rayon cat-concurrency

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

rayon cat-concurrency

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_anyrayon⮳ 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

rayon rand cat-concurrency

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

rayon cat-concurrency

This example uses rayon::iter::ParallelIterator::filterrayon::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

rayon glob image cat-concurrency cat-filesystem

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

One increasingly popular approach to ensuring safe concurrency is Message passing, where threads communicate by sending each other messages containing data. The Rust standard library provides channels for Message passing that are safe to use in concurrent contexts.

Message passing in async⮳ programming is covered in a separate page: async channels

Multiple producers, single consumer

std cat-concurrency

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

crossbeam_channel cat-concurrency

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

cat-concurrency

crossbeam

postage

Message passing (rust book)

Shared-State Concurrency

RecipeCratesCategories
Mutexescat-concurrency
Parking Lotcat-concurrency
Atomicscat-concurrency
arc-swapcat-concurrency

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

std cat-concurrency

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 parking_lot-crates.io cat-concurrency

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

std crossbeam cat-concurrency

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

arc-swap arc-swap-crates.io arc-swap-github arc-swap-lib.rs

The ArcSwap type is a container for an Arc that can be changed atomically. Semantically, it is similar to something like Atomic<Arc> (if there was such a thing) or RwLock<Arc> (but without the need for the locking). It is optimized for read-mostly scenarios, with consistent performance characteristics.

Concurrent Data Structures

Dashmap

dashmap cat-concurrency

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

cat-concurrency

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

flurry flurry-crates.io flurry-github flurry-lib.rs

Particularly good for read-heavy workloads.

conc map bench comparative benchmarks of concurrent HashMaps.

Configuration

Facilitate configuration management for applications.

Configuration Management

Environment Variables

Environment variables

Dotenvy

dotenvy cat-config

dotenvy⮳ supersedes dotenv⮳.

use std::env;

use anyhow::Result;

fn main() -> Result<()> {
    // Load environment variables from .env file.
    // Fails if .env file not found, not readable or invalid.
    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 cat-config

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

cat-config

dotenv

Configuration

Config

config cat-config

config⮳ is a layered configuration system for Rust applications. It reads from JSON, TOML, YAML, INI, RON, JSON5 files.

Confy

confy cat-config

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

RecipeCratesCategories
Docker
Docker Compose

Docker

References

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

RecipeCratesCategories
Develop across platforms with Cruxcrux_core

Cross-platform development with Crux

RecipeCratesCategories
Develop across platforms with Cruxcrux_core

Develop across platforms with Crux

crux_core crux_core-github

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

cat-cryptography

Securing data.

Encryption

RecipeCratesCategories
Salt and hash a password with PBKDF2ring data-encodingcat-cryptography

Hashing

Password Hashing

RecipeCratesCategories
argon2cat-cryptography
scryptcat-cryptography
bcryptcat-cryptography

Hashing

Calculate the SHA-256 digest of a file

ring data-encoding cat-cryptography

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

ring cat-cryptography

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(())
}

Password Hashing

RecipeCratesCategories
argon2cat-cryptography
scryptcat-cryptography
bcryptcat-cryptography

argon2

argon2 argon2-crates.io argon2-github argon2-lib.rs cat-authentication cat-cryptography cat-no-std

Pure Rust implementation of the Argon2 password hashing function with support for the Argon2d, Argon2i, and Argon2id algorithmic variants

scrypt

scrypt scrypt-crates.io scrypt-github scrypt-lib.rs cat-authentication cat-cryptography cat-no-std

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

bcrypt bcrypt-crates.io bcrypt-github bcrypt-lib.rs

Hash and verify passwords.

For more algorithms, see Rust Crypto Password Hashes

Encryption

RecipeCratesCategories
Salt and hash a password with PBKDF2ring data-encodingcat-cryptography

Salt and hash a password with PBKDF2

ring data-encoding cat-cryptography

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

Dataframes

RecipeCratesCategories
Manipulate data in a tabular formatpolars

Data Engineering

Data Visualization

RecipeCratesCategories
Plot and graph data

CSV files

Read and write CSV files

csv

Manipulate CSV files from the command line

xsv xsv-crates.io xsv-github xsv-lib.rs

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

RecipeCratesCategories
Manipulate data in a tabular formatpolars

Manipulate data in a tabular format

polars polars-book polars-github

Similar to the Pandas library in Python but in pure Rust. Uses the Apache Arrow Columnar Format as the memory model.

docs.pola.rs

Data Visualization

RecipeCratesCategories
Plot and graph data

Plot and graph data

plotly plotly-crates.io plotly-github plotly-lib.rs

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.

plotly.rs

Data Engineering

Develop data analytics applications that process columnar data with Arrow

arrow arrow-crates.io arrow-github arrow-lib.rs

arrow is the official Rust implementation of Apache Arrow

Query in-memory data with datafusion

datafusion datafusion-crates.io datafusion-github datafusion-lib.rs

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

cat-data-structures

Rust implementations of ways of organizing data suited for specific purposes.

Bitflags

Hashmaps and friends

RecipeCratesCategories
Insertion-ordered mapindexmapcat-data-structures
Multimapmultimapcat-data-structures
Slotmapslotmapcat-data-structures

Stack-allocated arrays

RecipeCratesCategories
arrayvecarrayveccat-data-structures
smallvecsmallveccat-data-structures
tinyvectinyveccat-data-structures

UUIDs

RecipeCratesCategories
Generate and parse UUIDsuuidcat-data-structures

Custom

Define and operate on a type represented as a bitfield

bitflags cat-no-std cat-data-structures

bitflags bitflags-crates.io bitflags-github bitflags-lib.rs

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 flagset-crates.io flagset-github flagset-lib.rs

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

RecipeCratesCategories
Insertion-ordered mapindexmapcat-data-structures
Multimapmultimapcat-data-structures
Slotmapslotmapcat-data-structures

Insertion-ordered map

indexmap indexmap-crates.io indexmap-github indexmap-lib.rs cat-data-structures cat-no-std

A HashMap that seperately keeps track of insertion order and allows you to efficiently iterate over its elements in that order

Multimap

multimap multimap-crates.io multimap-github multimap-lib.rs

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

slotmap slotmap-crates.io slotmap-github slotmap-lib.rs

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

Splay tree

Stack-allocated arrays

RecipeCratesCategories
arrayvecarrayveccat-data-structures
smallvecsmallveccat-data-structures
tinyvectinyveccat-data-structures

arrayvec

arrayvec arrayvec-crates.io arrayvec-github arrayvec-lib.rs cat-data-structures cat-no-std

Arrays that are ONLY stack-allocated with fixed capacity.

smallvec

smallvec smallvec-crates.io smallvec-github smallvec-lib.rs cat-data-structures

Arrays that are stack-allocated with fallback to the heap if the fixed stack capacity is exceeded.

tinyvec

tinyvec tinyvec-crates.io tinyvec-github tinyvec-lib.rs cat-data-structures cat-no-std

Stack-allocated arrays in 100% safe Rust code. tinyvec requires items to implement the Default trait.

UUID

RecipeCratesCategories
Generate and parse UUIDsuuidcat-data-structures

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 uuid-crates.io uuid-github uuid-lib.rs cat-data-structures cat-parser-implementations cat-no-std

uuid generates and parses UUIDs and implements a number of utility functions.

Databases

cat-database

Interface with database management systems.

SQLite

Postgres

Connection pools

RecipeCratesCategories
Create a connection pooldeadpoolcat-database

NoSQL and friends

RecipeCratesCategories
Connect to MongoDBmongodbcat-asynchronous cat-database cat-web-programming
Connect to Redisrediscat-database

Search

RecipeCratesCategories
Elasticsearchelasticsearchcat-api-bindings cat-database
Infisearchinfisearchcat-command-line-utilities
Stork searchstork-searchcat-wasm
Minisearchminisearch
Typesensetypesense{{hi:typesense}}
Tinysearchtinysearch{{hi:tinysearch}}

Query Builders and ORMs

RecipeCratesCategories
SQLxsqlxcat-database
Dieseldieselcat-database
SeaORMsea-ormcat-database
Toastytoastycat-database

SQLite

Create a SQLite database

rusqlite rusqlite-documentation cat-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 cat-database

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 cat-database

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

Create tables in a Postgres database

postgres cat-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

postgres cat-database

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

postgres cat-database csv-sample

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

tokio-postgres tokio-postgres-crates.io tokio-postgres-github tokio-postgres-lib.rs

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

RecipeCratesCategories
Create a connection pooldeadpoolcat-database

Create a connection pool

deadpooldeadpool-crates.iodeadpool-githubdeadpool-lib.rs

deadpool is a simple async pool for connections and objects of any type.

Query builders and ORMs

RecipeCratesCategories
SQLxsqlxcat-database
Dieseldieselcat-database
SeaORMsea-ormcat-database
Toastytoastycat-database

Sqlx

sqlx sqlx-github sqlx-lib.rs cat-database

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

sea-orm sea_orm-website sea_orm-cookbook cat-database

Seaography GraphQL server

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

diesel diesel-lib.rs cat-database

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 toasty-crates.io toasty-github toasty-lib.rs

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

RecipeCratesCategories
Connect to MongoDBmongodbcat-asynchronous cat-database cat-web-programming
Connect to Redisrediscat-database

Connect to MongoDB

mongodb mongodb-crates.io mongodb-github mongodb-lib.rs cat-asynchronous cat-database cat-web-programming

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 redis-crates.io redis-github redis-lib.rs

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

RecipeCratesCategories
Elasticsearchelasticsearchcat-api-bindings cat-database
Infisearchinfisearchcat-command-line-utilities
Stork searchstork-searchcat-wasm
Minisearchminisearch
Typesensetypesense{{hi:typesense}}
Tinysearchtinysearch{{hi:tinysearch}}

Elasticsearch

elasticsearch elasticsearch-crates.io elasticsearch-github elasticsearch-lib.rs cat-api-bindings cat-database

Infisearch

infisearch infisearch-crates.io infisearch-github infisearch-lib.rs cat-command-line-utilities

stork-search stork-search-crates.io stork-search-github stork-search-lib.rs cat-wasm

stork-search.net

Minisearch

minisearch minisearch-crates.io minisearch-github minisearch-lib.rs

minisearch-client-side-fulltext-search-engine

Typesense

typesense typesense-crates.io typesense-github typesense-lib.rs

Tinysearch

tinysearch tinysearch-crates.io tinysearch-github tinysearch-lib.rs

tinysearch

A Tiny, Static, Full-Text Search Engine using Rust and WebAssembly

Date and Time

cat-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

Parsing and Displaying

Using the time crate

RecipeCratesCategories
Use the time cratetimecat-date-and-time cat-value-formatting cat-parser-implementations cat-no-std

Duration and Calculation

Measure the elapsed time between two code sections

std cat-date-and-time

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

chrono cat-date-and-time

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

chrono cat-date-and-time

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

Examine the date and time

chrono cat-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

chrono cat-date-and-time

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

chrono cat-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

chrono cat-date-and-time

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_rfc2822chrono::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::NaiveDatechrono::naive::NaiveTime⮳ and chrono::naive::NaiveDateTime⮳.

#![allow(unused)]

fn main() {
}

Time

RecipeCratesCategories
Use the time cratetimecat-date-and-time cat-value-formatting cat-parser-implementations cat-no-std

Use the time crate

time-websitetimetime-crates.iotime-githubtime-lib.rscat-no-stdcat-date-and-timecat-parser-implementationscat-value-formatting cat-date-and-time cat-value-formatting cat-parser-implementations cat-no-std

Date and time library. Fully interoperable with the standard library. Mostly compatible with #![no_std].

Tools

cat-development-tools

Tools that provide developer-facing features such as testing, debugging, linting, performance profiling, autocompletion, formatting, and more.

Rust tools

Cargo

RecipeCratesCategories
Crate Registriescat-development-tools
RecipeCratesCategories
Package layout

Documentation

Formatting

Installation

RecipeCratesCategories
Rustupcat-development-tools
Cargo installcat-development-tools
Cargo binstallcat-development-tools
RecipeCratesCategories
Key commandscat-development-tools

Other

RecipeCratesCategories
Miricat-development-tools
Miri installationcat-development-tools

Versioning

Cargo, the Rust Package Manager

Basic cargo usage

The Cargo bookcat-development-tools

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

RecipeCratesCategories
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 and src/lib.rs, it has two crates: a binary and a library, both with the same name as the package.

Crate Registries

RecipeCratesCategories
Crate Registriescat-development-tools

In Rust, a library or executable program is called a crate. Crates are compiled using the Rust compiler, rustc rustc⮳.

Crate Registries

cat-development-tools

The Rust community’s crate registry: crates.io

Alternative to crates.io: lib.rs

Installing

RecipeCratesCategories
Rustupcat-development-tools
Cargo installcat-development-tools
Cargo binstallcat-development-tools

Rustup

Rustupcat-development-tools

Rustup installs, manages, and upgrades versions of rustc, cargo, clippy, rustfmt, etc.

Cargo install

cat-development-tools

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

cat-development-tools

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

RecipeCratesCategories
Key commandscat-development-tools

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

Rustup command examples

See also

Rustup documentation

Text Editors

cat-text-editors

Applications for editing text.

Formatting and Linting

Rustfmt

rustfmt_nightly-github cat-development-tools

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

Documenting your code

  • Add documentation comments to 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

The rustdoc book

docs.rs⮳: open-source documentation host for Rust crates.

mdBook

mdBook

mdbook-github cat-development-tools

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

mdBook documentation

Playground

Playground (Rust by example)Rust by example - Playground cat-development-tools

mdbook plugins

mdbook_hide-github cat-development-tools

CD / CI

GitHub Actions for mdBook

Badges

RecipeCratesCategories
BadgesShield.iocat-development-tools

cat-development-tools

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.

Shield.io

Versioning

Parse and increment a version string

semver cat-config 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

semver cat-config

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

semver cat-config

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

semver cat-config

Given a list of version &strs, finds the latest semver::Versionsemver::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

semver cat-text-processing cat-os

Runs git --version using std::process::Command⮳ then parses the version number into a semver::Version⮳ using semver::Version::parsesemver::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

Background code checker

bacon bacon-crates.io bacon-github bacon-lib.rs cat-development-tools cat-command-line-utilities

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

Roogleroogle-github cat-development-tools

Deployment

shuttle.rscat-development-tools

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 kani-crates.io kani-github kani-lib.rs

kani⮳ is a Rust verifier.

Formal Methods

rust-formal-methods.github.io

Miri Interpreter

RecipeCratesCategories
Miricat-development-tools
Miri installationcat-development-tools

Miri

miri-github cat-development-tools

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

https://just.systems/cat-development-tools

just is a command runner / Make replacement.

Just Programmer's Manual

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

cat-development-tools::build-utils

Utilities for build scripts and other build time steps.

Build Time Tooling

This section covers "build-time" tooling, or code that is run prior to compiling a crate's source code. Conventionally, build-time code lives in a build.rs file and is commonly referred to as a "build script". Common use cases include rust code generation and compilation of bundled C/C++/asm code. See crates.io's documentation on the matter⮳ for more information.

Compile and link statically to a bundled C library

cc cat-development-tools cat-development-tools::build-utils

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::flags⮳.

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

cc cat-development-tools

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

cc cat-development-tools

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

cat-development-tools::cargo-plugins

Subcommands that extend the capabilities of Cargo.

Writing code

Formatting and Linting

Building

Watching for changes

RecipeCratesCategories
Cargo watchcat-development-tools::cargo-plugins
Cargo limitcat-development-tools::cargo-plugins

Cross-compiling

Auditing

Performance

Maintenance

See also

  • Testing
  • Build Utils
  • Debugging
  • FFI
  • Procedural Macro Helpers
  • Profiling

Write code

Generate a Rust project from a template

cargo-generate cargo-generate-crates.io cargo-generate-github cargo-generate-lib.rs

cat-development-tools cat-development-tools::cargo-plugins

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 cargo-crates-crates.io cargo-crates-github cargo-crates-lib.rs

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

Format your code

rustfmt-nightly rustfmt-nightly-crates.io rustfmt-nightly-github rustfmt-nightly-lib.rs cat-development-tools cat-development-tools::cargo-plugins

# 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 clippy-crates.io clippy-github clippy-lib.rs cat-development-tools cat-development-tools::cargo-plugins

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

rustfix rustfix-crates.io rustfix-github rustfix-lib.rs cat-development-tools cat-development-tools::cargo-plugins

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 cargo-husky-crates.io cargo-husky-github cargo-husky-lib.rs cat-development-tools

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

cargo make

cargo-make cargo-make-crates.io cargo-make-github cargo-make-lib.rs cat-development-tools::testing cat-development-tools cat-command-line-utilities cat-development-tools::cargo-plugins cat-development-tools::build-utils

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 cargo-xtask-crates.io cargo-xtask-github cargo-xtask-lib.rs

cargo-xtask⮳ adds free-form automation to a Rust project, a-la makenpm run or bespoke bash scripts.

The two distinguishing features of xtask are the following:

  • It doesn't require any other binaries besides cargo and rustc, 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 xshell-crates.io xshell-github xshell-lib.rs

xshell provides a set of cross-platform utilities for writing cross-platform and ergonomic "bash" scripts.

Cross-compile

Cross-compile using Zig as the linker

cargo-zigbuild cargo-zigbuild-crates.io cargo-zigbuild-github cargo-zigbuild-lib.rs

cargo install --locked cargo-zigbuild

Watching for changes

RecipeCratesCategories
Cargo watchcat-development-tools::cargo-plugins
Cargo limitcat-development-tools::cargo-plugins

Cargo watch

cargo-watch cargo-watch-crates.io cargo-watch-github cargo-watch-lib.rs cat-development-tools cat-development-tools::cargo-plugins

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 cargo-limit-crates.io cargo-limit-github cargo-limit-lib.rs

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

Configure your Cargo project for maximum performance, fast compile times or minimal binary size

cargo-wizard cargo-wizard-crates.io cargo-wizard-github cargo-wizard-lib.rs cat-development-tools::cargo-plugins

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 cargo-hakari-crates.io cargo-hakari-github cargo-hakari-lib.rs

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-audit cargo-audit-crates.io cargo-audit-github cargo-audit-lib.rs cat-development-tools cat-development-tools::cargo-plugins

cargo install cargo-audit
cargo audit

Embded the exact crate versions in your Rust executable for auditability

cargo-auditable cargo-auditable-crates.io cargo-auditable-github cargo-auditable-lib.rs cat-development-tools::cargo-plugins cat-encoding

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-license cargo-license-crates.io cargo-license-github cargo-license-lib.rs cat-development-tools::cargo-plugins

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 cargo-deny-crates.io cargo-deny-github cargo-deny-lib.rs cat-development-tools::cargo-plugins

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_edit-github

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

cargo-udeps cargo-udeps-crates.io cargo-udeps-github cargo-udeps-lib.rs cat-development-tools cat-development-tools::cargo-plugins

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 cargo-machete-crates.io cargo-machete-github cargo-machete-lib.rs

cargo-machete is a Cargo tool that detects unused dependencies in Rust projects, in a fast (yet imprecise) way.

Machete

Install and run with:

cargo install cargo-machete
cargo machete

Detect dependencies that are out of date

cargo-outdated cargo-outdated-crates.io cargo-outdated-github cargo-outdated-lib.rs

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 cargo-semver-checks-crates.io cargo-semver-checks-github cargo-semver-checks-lib.rs cat-command-line-utilities cat-development-tools::cargo-plugins

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 cargo-cache-crates.io cargo-cache-github cargo-cache-lib.rs cat-development-tools cat-command-line-utilities cat-development-tools::cargo-plugins

cargo cache⮳ manages the cargo cache ($CARGO_HOME or ~/.cargo/), shows sizes and removes directories selectively.

Debugging, Logging

cat-development-tools::debugging

Help you figure out what is going on with your code such as logging, tracing, or assertions.

Tracing

Logging

Log Configuration

Alternatives

Diagnostic functions

Logs

tracing tracing-github cat-development-tools cat-development-tools::debugging

tracing_subscriber tracing_subscriber-crates.io

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;
}

tracing_journald tracing_journald-crates.io tracing_journald-github tracing_journald-lib.rs

tracing_journald⮳ provides support for logging tracing⮳ events natively to journald⮳, preserving any structured information.

Log Messages

Log a debug message to the console

log env_logger cat-development-tools::debugging

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

log env_logger cat-development-tools::debugging

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

log env_logger cat-development-tools::debugging

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

log cat-development-tools::debugging

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

log syslog cat-development-tools::debugging

Logs messages to UNIX syslog⮳. Initializes logger backend with syslog::initsyslog::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

Enable log levels per module

log env_logger cat-development-tools::debugging

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

log env_logger cat-development-tools::debugging

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

log env_logger chrono cat-development-tools::debugging

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

log log4rs cat-development-tools::debugging

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

Older alternatives to tracing

Tracing is now the "go-to" crate for logging.

log

log

log is an older and simpler crate if your needs are simple and you are not using any async code.

slog

slog slog-crates.io slog-github slog-lib.rs cat-development-tools::debugging

Structured, extensible, composable logging.

slog remains a stable, featureful and battle-tested library, used in many important projects.

log4rs

log4rs log4rs-crates.io log4rs-github log4rs-lib.rs

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

env_logger env_logger-crates.io env_logger-github env_logger-lib.rs cat-development-tools::debugging

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

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.
}

DevOps and Rust

DevOps

RecipeCratesCategories
Daytona

Dependency Management

RecipeCratesCategories
deps.rs
Rust Digger

Git Hooks

GitHub Actions

Release Automation

RecipeCratesCategories
cargo release
release-plz

DevOps + Rust

RecipeCratesCategories
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

RecipeCratesCategories
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

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 cargo-husky-crates.io cargo-husky-github cargo-husky-lib.rs cat-development-tools

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.

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. Use pre-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

Rust CI Tooling: Clippy, commitlint, pre‑commit

pre-commit-rust

A pre-commit hook for commitlint

GitHub Actions

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

rust-cache

rust-cache rust-cache-crates.io rust-cache-github rust-cache-lib.rs

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: ""

Release automation

RecipeCratesCategories
cargo release
release-plz

cargo release

Helper for publishing new crate versions.

cargo-release cargo-release-crates.io cargo-release-github cargo-release-lib.rs cat-development-tools::cargo-plugins

release-plz

release-plz release-plz-crates.io release-plz-github release-plz-lib.rs cat-development-tools cat-command-line-utilities

Release Rust crates from CI with a Release PR.

Continuous Deployment / Continuous Integration

RecipeCratesCategories
CD / CI

Continuous Integration (cargo book)

See also

Optimizing CI/CD pipelines

Creating a docker container action

"Upload a Build Artifact" Github Action

docker-cache

Cached Docker images

Docker GitHub Action

Cache storage backends

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)

Experience about deploying mdbook on github using github action - tutorials - The Rust Programming Language Forum

Encoding

cat-encoding

Encoding and/or decoding data from one data format to another.

Character Sets

CSV Processing

Structured Data

Serde

RecipeCratesCategories
JSONcat-encoding
serde-jsoncat-encoding
monostatecat-encoding
serde-ignoredcat-encoding

Character Sets

Percent-encode a string

percent_encoding cat-encoding

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

urlurl-crates.iourl-githuburl-lib.rscat-encodingcat-no-stdcat-parser-implementationscat-web-programming

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

data-encoding cat-encoding

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

base64 cat-encoding

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

csv cat-encoding

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

csv cat-encoding

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

csv cat-encoding

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 serde cat-encoding

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

csv cat-encoding

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

csv serde cat-encoding

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

csv serde cat-encoding

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

Serialize and deserialize unstructured JSON

serde_json cat-encoding

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

toml cat-encoding

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 cat-encoding

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

RecipeCratesCategories
JSONcat-encoding
serde-jsoncat-encoding
monostatecat-encoding
serde-ignoredcat-encoding

serde serde-crates.io serde-github serde-lib.rs

De facto standard serialization library. Use in conjunction with sub-crates like serde_json for the specific format that you are using.

JSON

serde_json serde_json-crates.io serde_json-github serde_json-lib.rs

See also

monostate

monostate monostate-crates.io monostate-github monostate-lib.rs

This library implements a type macro for a zero-sized type that is Serde deserializable only from one specific value.

serde-ignored

serde-ignored serde-ignored-crates.io serde-ignored-github serde-ignored-lib.rs

Typecasts

RecipeCratesCategories
bytemuckbytemuckcat-encoding
zerocopyzerocopycat-encoding

bytemuck

bytemuck bytemuck-crates.io bytemuck-github bytemuck-lib.rs cat-encoding cat-no-std cat-encoding

zerocopy

zerocopy zerocopy-crates.io zerocopy-github zerocopy-lib.rs cat-encoding cat-parsing cat-rust-patterns cat-embedded cat-no-std::no-alloc cat-encoding

Error Handling

Irrecoverable panics

cat-rust-patterns

fn main() {
    panic!("crash and burn");
}

Recoverable errors with Result

cat-rust-patterns

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

cat-rust-patterns


## 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

cat-rust-patterns

Handles error that occur when trying to open a file that does not exist. It is achieved by using error_chain⮳, a library that takes care of a lot of boilerplate code needed in order to handle errors in Rust⮳.

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

error_chain cat-rust-patterns

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

error_chain cat-rust-patterns

This recipe shows how to handle a complex error scenario and then print a backtrace. It relies on chain_err⮳ to extend errors by appending new errors. The error stack can be unwound, thus providing a better context to understand why an error was raised.

The below recipes attempts to deserialize the value 256 into a u8⮳. An error will bubble up from Serde then csv and finally up to the user code.

// 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

RecipeCratesCategories
Anyhowcat-rust-patterns
thisErrorcat-rust-patterns
miettecat-rust-patterns
color-eyrecat-rust-patterns

Use anyhow⮳ if you don't care what error type your functions return, you just want it to be easy. This is common in application code. Use thiserror⮳ if you are a library that wants to design your own dedicated error type(s) so that on failures the caller gets exactly the information that you choose.

Anyhow

anyhow anyhow-crates.io cat-rust-patterns

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

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

miette miette-lib.rs 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

color-eyre color-eyre-crates.io color-eyre-github color-eyre-lib.rs

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

eyre

error_chain

Do not use Error Chain⮳, which is deprecated.

File System

cat-filesystem

Dealing with files and file systems.

File Reading & Writing

Current Working Directory

RecipeCratesCategories
Get the current working directorycat-filesystem

User Directories

Temporary Files and Directories

Directory Traversal

Walk the Filesystem while Respecting Ignore Files

File Watching

Read & Write

Read lines of strings from a file

std cat-filesystem

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::linesstd::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

same-file cat-filesystem

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

memmap2 cat-filesystem

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

RecipeCratesCategories
Get the current working directorycat-filesystem

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

std cat-filesystem

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

same-file same-file-crates.io same-file-github same-file-lib.rs cat-filesystem

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

cat-filesystem

walkdir walkdir-crates.io walkdir-github walkdir-lib.rs

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

walkdir cat-filesystem

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

walkdir cat-filesystem

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

walkdir cat-filesystem

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

glob cat-filesystem

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

glob cat-filesystem

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(())
}

Walk the filesystem

Walk the filesystem while respecting ignore files

ignore ignore-crates.io ignore-github ignore-lib.rs

Recursive filesystem walking that respects ignore files (like .gitignore)

File watching

Watch files or directories and execute a function when they change

notify notify-crates.io notify-github notify-lib.rs cat-filesystem


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

Create temporary files or temporary directories

tempfile tempfile-crates.io tempfile-github tempfile-lib.rs cat-filesystem

Supports both temporary files and temporary directories.

User directories

Get platform-specific locations for configuration, cache, and other data

dirs

dirs dirs-crates.io dirs-github dirs-lib.rs cat-filesystem

Provide platform-specific locations for configuration, cache, and other data

directories

directories directories-crates.io directories-github directories-lib.rs cat-filesystem

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

RecipeCratesCategories
rust-gpurust_gpu-github

Use GPUs with Rust

RecipeCratesCategories
rust-gpurust_gpu-github

rust-gpu

rust_gpu-github

Hardware Support

cat-hardware-support

Interface with specific CPU or other hardware features.

Processor

RecipeCratesCategories
Get the number of logical cpu coresnum_cpuscat-hardware-support

Processor

RecipeCratesCategories
Get the number of logical cpu coresnum_cpuscat-hardware-support

Check number of logical cpu cores

num_cpus num_cpus-crates.io num_cpus-github num_cpus-lib.rs cat-hardware-support

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

RecipeCratesCategories
Linfalinfacat-science
Candlecandle-corecat-science
Otherscat-science

Are we learning yet?

Linfa

linfa linfa-crates.io linfa-website cat-science

Candle

candle-corecandle-core-crates.iocandle-core-githubcandle-core-lib.rscat-science

Candle: a minimalist machine learning framework for rust that focuses on performance including GPU support and ease of use

Others

Smartcore

smartcore-websitesmartcoresmartcore-crates.iosmartcore-githubsmartcore-lib.rscat-science

SmartCore⮳ Machine Learning in Rust.

Watchmaker

Watchmaker (genetic algos in Rust)watchmaker watchmaker-github watchmaker-crates.io cat-science

Mathematics

cat-mathematics

Crates with a mathematical aspect.

Linear algebra

Trigonometry

Complex numbers

Statistics

Additional numeric types

Linear Algebra

Adding matrices

ndarray ndarray-crates.io ndarray-github ndarray-lib.rs cat-data-structures cat-science

Creates two 2-D matrices with ndarray::arr2⮳ and sums them element-wise.

Note the sum is computed as let sum = &a + &b. The & operator is used to avoid consuming a and b, making them available later for display. A new array is created containing their sum.

use ndarray::arr2;

fn main() {
    let a = arr2(&[[1, 2, 3], [4, 5, 6]]);

    let b = arr2(&[[6, 5, 4], [3, 2, 1]]);

    let sum = &a + &b;

    println!("{}", a);
    println!("+");
    println!("{}", b);
    println!("=");
    println!("{}", sum);
}

Multiplying matrices

ndarray ndarray-crates.io ndarray-github ndarray-lib.rs cat-data-structures cat-science

Creates two matrices with ndarray::arr2⮳ and performs matrix multiplication on them with ndarray::ArrayBase::dot⮳.

use ndarray::arr2;

fn main() {
    let a = arr2(&[[1, 2, 3], [4, 5, 6]]);

    let b = arr2(&[[6, 3], [5, 2], [4, 1]]);

    println!("{}", a.dot(&b));
}

Multiply a scalar with a vector and a matrix

ndarray ndarray-crates.io ndarray-github ndarray-lib.rs cat-data-structures cat-science

Creates a 1-D array (vector) with ndarray::arr1⮳ and a 2-D array (matrix) with ndarray::arr2

First, a scalar is multiplied by the vector to get another vector. Then, the matrix is multiplied by the new vector with ndarray::Array2::dot⮳ (Matrix multiplication is performed using 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

ndarray ndarray-crates.io ndarray-github ndarray-lib.rs cat-data-structures cat-science

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

ndarray ndarray-crates.io ndarray-github ndarray-lib.rs cat-data-structures cat-science

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. + The l1_norm⮳ function is computed by a ndarray::ArrayBase::fold⮳ operation that sums the absolute values of the elements. (This could also be performed with x.mapv(f64::abs).scalar_sum(), but that would allocate a new array for the result of the mapv.)

Note that both l1_norm⮳ and l2_norm⮳ take the 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

nalgebra nalgebra-crates.io nalgebra-github nalgebra-lib.rs cat-mathematics cat-no-std cat-science cat-wasm

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

ndarray ndarray-crates.io ndarray-github ndarray-lib.rs cat-data-structures cat-science

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(())
}

Trigonometry

Calculating the side length of a triangle

std cat-science

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

std cat-science

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

std

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. sincospowi⮳ 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

Creating complex numbers

num num-crates.io num-github num-lib.rs cat-science cat-algorithms cat-no-std cat-data-structures

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

num num-crates.io num-github num-lib.rs cat-science cat-algorithms cat-no-std cat-data-structures

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

num num-crates.io num-github num-lib.rs cat-science cat-algorithms cat-no-std cat-data-structures

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

Measures of central tendency

std cat-science

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

std cat-science

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

Abstracting over different number types

num-traits num-traits-crates.io num-traits-github num-traits-lib.rs cat-algorithms cat-science cat-no-std

Traits like Number, Add, etc that allow you write functions that are generic over the specific numeric type

Big Integers

num

num num-crates.io num-github num-lib.rs cat-science cat-algorithms cat-no-std cat-data-structures

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

num-bigint num-bigint-crates.io num-bigint-github num-bigint-lib.rs cat-algorithms cat-data-structures cat-science

It's not the fastest, but it's part of the trusted num library.

rug

rug rug-crates.io rug-github rug-lib.rs cat-api-bindings cat-mathematics

LGPL licensed. Wrapper for GMP. Much faster than num-bigint

Big decimal

rust_decimal rust_decimal-crates.io rust_decimal-github rust_decimal-lib.rs cat-data-structures cat-science cat-mathematics

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

ordered-float ordered-float-crates.io ordered-float-github ordered-float-lib.rs cat-rust-patterns cat-science cat-no-std

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

cat-memory-management

Deal with allocation, memory mapping, garbage collection, reference counting, or interfaces to foreign memory managers.

RecipeCratesCategories
Declare lazily evaluated constantlazy_staticcat-caching cat-rust-patterns cat-memory-management
RecipeCratesCategories
stdcat-memory-management
once_cellonce_cellcat-memory-management
lazy_staticlazy_staticcat-memory-management

Global static

RecipeCratesCategories
Declare lazily evaluated constantlazy_staticcat-caching cat-rust-patterns cat-memory-management

Declare lazily evaluated constant

lazy_static lazy_static-crates.io lazy_static-github lazy_static-lib.rs cat-caching cat-rust-patterns cat-memory-management

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

RecipeCratesCategories
stdcat-memory-management
once_cellonce_cellcat-memory-management
lazy_staticlazy_staticcat-memory-management

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

std cat-memory-management

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 once_cell-crates.io once_cell-github once_cell-lib.rs cat-memory-management cat-rust-patterns

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

lazy_static lazy_static-crates.io lazy_static-github lazy_static-lib.rs cat-no-std cat-memory-management cat-rust-patterns

Operating System

cat-os

Bindings to operating system-specific APIs.

RecipeCratesCategories
Call libccat-os
RecipeCratesCategories
Bottlerocketbottlerocketcat-os
Redoxcat-os

External Command

Run an external command and process stdout

regex cat-os cat-text-processing

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

std cat-os

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

std cat-os

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

std cat-os

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

std cat-os

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

std cat-os

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 duct-crates.io duct-github duct-lib.rs

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

RecipeCratesCategories
Call libccat-os

Call libc

libc libc-crates.io libc-github libc-lib.rs cat-external-ffi-bindings cat-os cat-no-std

Bindings for directly calling libc functions.

Operating systems written in Rust

RecipeCratesCategories
Bottlerocketbottlerocketcat-os
Redoxcat-os

Bottlerocket

bottlerocketbottlerocket-crates.iobottlerocket-githubbottlerocket-lib.rs

bottlerocket⮳ is an operating system designed for hosting containers.

Redox

redox⮳ is a Rust Operating System.

Rust Patterns

cat-rust-patterns

Shared solutions for particular situations specific to programming in Rust.

RecipeCratesCategories
Anyhowcat-rust-patterns
thisErrorcat-rust-patterns
miettecat-rust-patterns
color-eyrecat-rust-patterns
RecipeCratesCategories
Abstract factorycat-rust-patterns
dyn-clonecat-rust-patterns
Lenscat-rust-patterns
RecipeCratesCategories
Compose iterators with itertoolscat-rust-patterns
RecipeCratesCategories
Rust idiomscat-rust-patterns

Design Patterns

RecipeCratesCategories
Abstract factorycat-rust-patterns
dyn-clonecat-rust-patterns
Lenscat-rust-patterns

Abstract factory

abstract-factory in rust

dyn-clone

dyn-clone dyn-clone-crates.io dyn-clone-github dyn-clone-lib.rs cat-rust-patterns cat-no-std

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

RecipeCratesCategories
Compose iterators with itertoolscat-rust-patterns

Compose iterators with itertools

itertools itertools-crates.io itertools-github itertools-lib.rs cat-algorithms cat-rust-patterns cat-no-std cat-no-std::no-alloc

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

RecipeCratesCategories
Rust idiomscat-rust-patterns

rust-unofficial-patterns

rust-state-machine-pattern

Scripting in Rust

Rust scripting

Embed Rust scripting in your application

rhai rhai-crates.io rhai-github rhai-lib.rs cat-embedded cat-parser-implementations cat-no-std cat-wasm

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

cat-template-engine

Crates designed to combine templates with data to produce result documents, usually with an emphasis on processing tex

Tera

RecipeCratesCategories
Create HTML files from a templateteracat-template-engine

TinyTemplate

RecipeCratesCategories
tinytemplatecat-template-engine

Tera

RecipeCratesCategories
Create HTML files from a templateteracat-template-engine

Create HTML files from a template

tera tera-crates.io tera-github tera-lib.rs cat-template-engine

Template engine based on Jinja2/Django templates

Tinytemplate

RecipeCratesCategories
tinytemplatecat-template-engine

tinytemplate

tinytemplate tinytemplate-crates.io tinytemplate-github tinytemplate-lib.rs cat-template-engine

Text Editors

cat-text-editors

Applications for editing text.

IDEs

VS Code

VS Code

Rust plugin for VS Code

rust-analyzer (home page)

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

zed zed-crates.io zed-github zed-lib.rs

Helix Editor

helix-editor.com

helix-editor-github

See also

6 IDEs built for rust

Text Processing

Deal with the complexities of human language when expressed in textual form.

cat-text-processing

RecipeCratesCategories
Benchmark

Regular Expressions

Verify and extract login from an email address

regex lazy_static cat-text-processing

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

regex lazy_static cat-text-processing

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 regextwitter-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

regex cat-text-processing

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

regex cat-text-processing

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

regex lazy_static cat-text-processing

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");
}

Longer Regex Example

regex regex-github cat-text-processing

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

Collect Unicode Graphemes

unicode-segmentationunicode-segmentation-crates.iounicode-segmentation-githubunicode-segmentation-lib.rs cat-text-processing

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

std cat-text-processing

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

cat-text-processing

RecipeCratesCategories
Benchmark

Benchmark

Web Programming

Create applications for the web.

cat-web-programming

Uniform Resource Locations (URL)

Media Types (MIME)

Scraping Web Pages

See also

Are we Web yet?

Building a crawler in Rust: Design and Associated Types

Extracting Links

reqwest select cat-network-programming cat-web-programming

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(())
}

reqwest select url cat-network-programming cat-web-programming

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(())
}

reqwest regex cat-network-programming cat-web-programming

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

url cat-network-programming

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

url cat-network-programming

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

url cat-network-programming

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)

url cat-network-programming

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

url cat-network-programming

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

Get MIME type from string

mime cat-encoding cat-web-programming

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

mime cat-encoding cat-web-programming

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

reqwest mime cat-network-programming cat-encoding cat-web-programming

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

cat-web-programming::http-client

Make HTTP network requests.

HTTP client libraries

RecipeCratesCategories
reqwestreqwestcat-web-programming::http-client
urequreqcat-web-programming::http-client
hyperhypercat-web-programming::http-client

Requests

APIs

Downloads

HTTP clients

RecipeCratesCategories
reqwestreqwestcat-web-programming::http-client
urequreqcat-web-programming::http-client
hyperhypercat-web-programming::http-client

reqwest

reqwest reqwest-crates.io reqwest-github reqwest-lib.rs

Full-fat HTTP client. Can be used in both synchronous and asynchronous code. Requires tokio runtime.

ureq

ureq ureq-crates.io ureq-github ureq-lib.rs cat-web-programming::http-client

Minimal synchronous HTTP client focussed on simplicity and minimising dependencies.

hyper

hyper hyper-crates.io hyper-github hyper-lib.rs

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

Make a HTTP GET request

reqwest cat-network-programming cat-web-programming

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

reqwest hyper url cat-network-programming cat-web-programming cat-web-programming::http-client

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_AGENThyper::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

Query the GitHub API

reqwest serde cat-network-programming cat-encoding cat-web-programming cat-web-programming::http-client

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

reqwest cat-network-programming cat-web-programming cat-web-programming::http-client

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

reqwest serde cat-network-programming cat-encoding cat-web-programming cat-web-programming::http-client

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

reqwest serde cat-network-programming cat-encoding cat-web-programming cat-web-programming::http-client

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

reqwest hyper cat-network-programming cat-web-programming cat-web-programming::http-client

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

Download a file to a temporary directory

reqwest tempfile cat-network-programming cat-filesystem cat-web-programming

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 cat-network-programming cat-web-programming cat-web-programming::http-client

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

reqwest cat-network-programming cat-web-programming cat-web-programming::http-client

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.

RecipeCratesCategories
Actixcat-web-programming cat-web-programming::http-server
RecipeCratesCategories
Axumcat-web-programming cat-web-programming::http-server
RecipeCratesCategories
Loco
Rust on Nails
RecipeCratesCategories
CORScat-web-programming
RecipeCratesCategories
async-graphql
RecipeCratesCategories
tonic
RecipeCratesCategories
hyper
RecipeCratesCategories
Towertowercat-web-programming cat-web-programming::http-server
Tower HTTPtower_httpcat-web-programming cat-web-programming::http-server
Alternatives
RecipeCratesCategories
Rocketcat-web-programming cat-web-programming::http-server
Leptoscat-web-programming::http-server

Axum

RecipeCratesCategories
Axumcat-web-programming cat-web-programming::http-server

cat-web-programming cat-web-programming::http-server

crates.io-example-source-code

Crates.io example source code (using Axum)

A minimal and ergonomic framework. An official part of the tokio project. Recommend for most new projects.

Actix

RecipeCratesCategories
Actixcat-web-programming cat-web-programming::http-server

cat-web-programming cat-web-programming::http-server

actix-web

Actix Web (website)

Actix examples: Actix examples

Auth Web Microservice with rust using actix_web 4.0 - Complete Tutorial

Practical Rust Web Development - API Rest

A performance focussed framework. All Rust frameworks are fast, but choose actix-web if you need the absolutely maximum performance.

Other Web Frameworks

RecipeCratesCategories
Rocketcat-web-programming cat-web-programming::http-server
Leptoscat-web-programming::http-server

cat-web-programming cat-web-programming::http-server

Rust web framework comparison

Rocket

rocket rocket-crates.io rocket-github rocket-lib.rs cat-web-programming::http-server

Web framework with a focus on usability, security, extensibility, and speed.

rocket-realworld-example

Rust + Rocket RealWorld framework implementation

Leptos

leptos leptos-crates.io leptos-github leptos-lib.rs cat-web-programming::http-server

leptos⮳ is a full-stack, isomorphic Rust web framework leveraging fine-grained reactivity to build declarative user interfaces.

Static Website Generators

Zola

zolazola-githubzola-lib.rs cat-web-programming cat-web-programming::http-server

Zola⮳ is a fast static site generator in a single binary with everything built-in.

Zola Themes

AdiDoks is a modern documentation theme.

Middleware

RecipeCratesCategories
Towertowercat-web-programming cat-web-programming::http-server
Tower HTTPtower_httpcat-web-programming cat-web-programming::http-server
Alternatives

Tower

tower cat-web-programming cat-web-programming::http-server

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-middleware-from-scratch

Tower HTTP

tower_http cat-web-programming cat-web-programming::http-server

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

trilliumtrillium-crates.iotrillium-githubtrillium-lib.rscat-web-programming::http-servercat-web-programming

A modular toolkit for building async web apps

CORS

RecipeCratesCategories
CORScat-web-programming

cat-web-programming cat-web-programming::http-server

CORS (mozilla)

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

RecipeCratesCategories
Loco
Rust on Nails

Loco

loco_rs loco_rs-crates.io loco_rs-github loco_rs-lib.rs cat-web-programming::http-server

Rust on Nails

Rust on Nails

See also

Building a SaaS with Rust and Next.js

GraphQL

RecipeCratesCategories
async-graphql

async-graphql

A high-performance graphql server library that's fully specification compliant. Integrates with actix-web, axum, poem, rocket, tide, warp.

async-graphql async-graphql-crates.io async-graphql-github async-graphql-lib.rs cat-asynchronous cat-network-programming

gRPC

RecipeCratesCategories
tonic

tonic

tonic tonic-crates.io tonic-github tonic-lib.rs cat-asynchronous cat-network-programming cat-web-programming

gRPC over HTTP/2 with full support for asynchronous code. Works with tokio

Hyper

RecipeCratesCategories
hyper

hyper

hyper hyper-crates.io hyper-github hyper-lib.rs

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

RecipeCratesCategories
Compilers
swc
Code editors
lapce
zed
Build tools

Python tools written in Rust

RecipeCratesCategories
rustpythoncat-api-bindings
pyOxidizercat-api-bindings
Ruff
uv

Others

Development tools written in Rust

RecipeCratesCategories
Compilers
swc
Code editors
lapce
zed
Build tools

Compilers

swc

swc swc-crates.io swc-github swc-lib.rs

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-website lapce-github lapce-lib.rs

lapce⮳ is a lightning-fast and Powerful Code Editor written in Rust.

Zed

zed-crates.io zed-github zed-lib.rs

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

RecipeCratesCategories
rustpythoncat-api-bindings
pyOxidizercat-api-bindings
Ruff
uv

rustpython

rustpython rustpython-crates.io rustpython-github rustpython-lib.rs

RustPython⮳ is a Python Interpreter written in Rust

pyOxidizer

pyOxidizer

Ruff

Ruff

uv

Other tools written in Rust

File managers

Spacedrive is an open source cross-platform file explorer, powered by a virtual distributed filesystem written in Rust. spacedrivespacedrive-github

Remote desktops

rustdesk rustdesk-crates.io rustdesk-github rustdesk-lib.rs

rustdesk⮳ is an open-source remote desktop application designed for self-hosting, as an alternative to TeamViewer.

Email applications

postsack

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

Lists of Rust links

Section
Rust Links

Example code

Recipe
example-code

Rust Cheatsheets

Cheatsheets

Comparison to other languages

Blogs, Podcasts, Meetups

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

Companies that use or contribute to Rust

Accelerant.dev

Cloudflare

Embark Studios

Ferrous Systems

Freiheit

Immunant

Mozilla

Learn Rust

Section
Rust Learning

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, 1
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:

This site is not affiliated with the Rust Foundation⮳.