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, logging...
Concurrency, including asynchronous programming, are 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 typically dry reference docs⮳, and hopes to become a "cheat sheet on steroid" for the Rust ecosystem (not just the Rust language).
What other books should I consult?
Rust by Example⮳ is similar in concept - a collection of runnable examples - but not in scope, as it focuses solely on the Rust language and standard libraries.
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.
Call for contributions
This book is in its early days - feel free to submit an issue or a pull request to the repo.
Contributions, from small edits to whole chapters, are most welcome. Draft pages are kept in this folder. An informal (and very long) list of topics of interest is kept in TODO. Embedded examples should be ideally runnable on the Rust playground⮳ or at least directly copy-pasteable into Rust code. Please read CONTRIBUTING.md for more details.
Its long-term goal is the coverage of the 'most commonly used' Rust crates, as defined by blessed.rs⮳, the most downloaded libraries in crates.io⮳, and 'high quality crates' per lib.rs⮳ statistics⮳. Review key crates for topic ideas.
This site is not affiliated with the Rust Foundation⮳.
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
.
- Main function
- Simple data types
- Variables and constants
- Ownership and borrowing
- Slices
- Functions
- Control flow
- Structs
- Enums
- Traits
- Trait objects
- Attributes
- Generics
- Lifetimes
- Modules and
use
keyword - Pattern matching, if / while let
- Closures
- Iterators
- Macros
Main function
fn main() { println!("Hello, world!"); }
Async Main Function
#[tokio::main] async fn main() -> Result<(), Box<dyn std::error::Error>> { println!("I'm async!"); Ok(()) }
Simple Data Types
- Integers:
i8
,i16
,i32
,i64
,i128
,isize
- Unsigned:
u8
,u16
,u32
,u128
,usize
usize
andisize
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;
- Access via
- Arrays:
let a: [i32; 5] = [1, 2, 3, 4, 5];
allocated on the stack. access vialet first = a[0];
- A vector is a similar collection type provided by the standard library that is allowed to grow or shrink in size
- Unit (aka void):
()
- Type aliases:
type Kilometers = i32;
Overflow handling
- Wrap in all modes with the
wrapping_*
methods, such aswrapping_add
. - Return the
None
value if there is overflow with thechecked_*
methods. - Return the value and a boolean indicating whether there was overflow with the
overflowing_*
methods. - Saturate at the value’s minimum or maximum values with the
saturating_*
methods.
Variables and Constants
#![allow(unused)] 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
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
fn main() { // destructuring tuples let (_x, _y, _z) = (1, 2, 3); // destructuring structs struct Point { x: i32, y: i32, } let p = Point { x: 0, y: 7 }; let Point { x: _a, y: _b } = p; // a = 0, b = 7 let Point { x, y } = p; // simpler let _ = (x, y); }
Starting the name of a variable with an underscore silences unused variable warnings.
Ownership and Borrowing
Ownership
- No garbage collector. Ownership instead.
- Each value in Rust has an owner.
- There can only be one owner at a time.
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.
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 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 Copy
trait (stack-only, fixed-size values, like integers, floats, and tuples thereof), variables that use it do not move, but rather are trivially copied, making them still valid after assignment to another variable.
fn main() { let x = 5; // Integer let y = x; // No MOVE println!("x = {}, y = {}", x, y); // OK }
Borrowing
Passing a variable to a function will move or copy, just as assignment does. To avoid passing a value along, borrow the value:
fn main() { let s1 = String::from("hello"); let _len = calculate_length(&s1); // pass an immutable reference to s1 fn calculate_length(s: &String) -> 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
fn main() { let s = String::from("hello world"); let hello: &str = &s[0..5]; // or &s[..5]; let world = &s[6..11]; // or &s[6..]; println!("{}", hello); println!("{}", world); }
Functions
fn foo(x: i32, unit_label: char) -> i32 { let y = { let z = 3; x + z // expression at the end of a block - no semi-colon }; println!("The value of y is: {y}{unit_label}"); y // returns y - no semi-colon } fn main() { println!("{}", foo(1, 'm')); }
The unit type ()
(void
in some languages) is the default return type when no type is given for a function.
It could be omitted: fn log(message: &str) { ... }
Generic functions
fn generic<T>(_t: T) { println!("got t"); } // Explicitly specified type parameter `char` to `generic()`. Note the // turbofish notation ::<> fn main() { generic::<char>('a'); }
use std::fmt::Display; fn generic<T: ?Sized + Display>(t: &T) { // By default, generic functions will work only on types that have a // known size at compile time. Use ?Sized to relax that. t must be // some kind of pointer: &, Rc, Box... println!("{}", t); } fn main() { let s = String::from("hello"); generic(&s[..]); }
Function pointers
fn add_one(x: i32) -> i32 { x + 1 } fn do_twice(f: fn(i32) -> i32, arg: i32) -> i32 { // function pointer f(arg) + f(arg) } fn main() { println!("{}", do_twice(add_one, 1)); }
Diverging functions
Diverging functions never return.
fn foo() -> ! { // <-- empty type panic!("This call never returns."); } fn main() { println!("Will panic"); foo(); }
Control Flow
If else
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
fn main() { let mut counter = 0; let result = loop { counter += 1; if counter == 10 { break counter * 2; // `continue` and loop labels also exist: // https://doc.rust-lang.org/book/ch03-05-control-flow.html } }; println!("{}", result); }
While
fn main() { let mut number = 5; while number != 0 { println!("{number}!"); number -= 1; } }
For
fn main() { let a = [10, 20, 30, 40, 50]; for element in a { println!("the value is: {element}"); } // Range - generates all numbers in sequence // starting from one number and ending before another number. for number in (1..4).rev() { // reverse enumeration println!("{number}!"); } }
Structs
#![allow(dead_code)] 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
.
#![allow(dead_code)] 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
#![allow(dead_code)] 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 } } fn main() {}
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(); } // BEWARE: supertrait are NOT inheritance!
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
#![allow(dead_code)] use std::clone::Clone; use std::fmt::Debug; // Note the `+` fn a_function(item: &(impl Debug + Clone)) { println!("{:?}", item.clone()); } fn some_function<T, U>(_t: &T, _u: &U) -> i32 where T: Debug + Clone, // note the `+` U: Debug + Clone, { 42 } #[derive(Debug, Clone)] struct S; fn main() { let s = S; a_function(&s); }
Return-position impl Trait
fn returns_closure() -> impl Fn(i32) -> i32 { |x| x + 1 } fn main() { let f = returns_closure(); println!("{}", f(1)); }
Generic traits
trait Test<T> { fn test(_t: T); } struct SomeStruct; impl<T> Test<T> for SomeStruct { // note the <> in two places fn test(_t: T) { println!("test"); } } fn main() { SomeStruct::test(1); SomeStruct::test(true); }
Associated types
trait Iterator { type Item; // <-- associated type // in Impl, use e.g. `Iterator<Item = u32>` fn next(&mut self) -> Option<Self::Item>; } // Generic type with default trait Add<Rhs = Self> { type Output; // <-- associated type fn add(self, rhs: Rhs) -> Self::Output; } fn main() {}
Trait bounds
use std::collections::hash_map::DefaultHasher; use std::hash::Hash; use std::hash::Hasher; // Trait bounds: the `print_hash` function is generic over an unknown // type `T`, but requires that `T` implements the `Hash` trait. fn print_hash<T: Hash>(t: &T) { let mut hasher = DefaultHasher::new(); t.hash(&mut hasher); println!("The hash is {:x}", hasher.finish()); } struct Pair<A, B> { first: A, second: B, } // Generics make it possible to implement a trait conditionally. // Here, the Pair type implements Hash if, and only if, // its components do. impl<A: Hash, B: Hash> Hash for Pair<A, B> { fn hash<H: Hasher>(&self, state: &mut H) { self.first.hash(state); self.second.hash(state); } } fn main() { let p = Pair { first: 1, second: "2", }; print_hash(&p); }
Constants in traits
trait Example { const CONST_NO_DEFAULT: i32; const CONST_WITH_DEFAULT: i32 = 99; } struct S; impl Example for S { const CONST_NO_DEFAULT: i32 = 0; } fn main() { println!("{} {}", S::CONST_NO_DEFAULT, S::CONST_WITH_DEFAULT); }
Async and traits
See Async
See also
Trait Objects
In Rust, traits are types, but they are "unsized", which roughly means that they are only allowed to show up behind a pointer like 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 Send
, Sync
, Unpin
, UnwindSafe
, and RefUnwindSafe
- see special traits⮳).
dyn Trait
dyn Trait + Send
dyn Trait + Send + Sync
dyn Trait + 'static
See also
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)] pub struct S; /// Here is the main 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
Must Use
// Must use the results of the fn; also applies to traits, structs, // enums... #[must_use] fn add(a: i32, b: i32) -> i32 { a + b } fn main() { println!("{}", add(1, 2)); }
Deprecated
#![allow(deprecated)] // Removes the warning. #[deprecated(since = "5.2.0", note = "use bar instead")] pub fn foo() {} fn main() { foo(); }
Conditional Compilation
// This function only gets compiled if the target OS is linux #[cfg(target_os = "linux")] fn are_you_on_linux() { println!("You are running linux!"); } // And this function only gets compiled if the target OS is *not* // linux #[cfg(not(target_os = "linux"))] fn are_you_on_linux() { println!("You are *not* running linux!"); } fn main() { are_you_on_linux(); println!("Are you sure?"); if cfg!(target_os = "linux") { // alternative: use cfg! println!("Yes. It's definitely linux!"); } else { println!("Yes. It's definitely *not* linux!"); } }
See Also
Generics
Generic Structs
#![allow(dead_code)] 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
Prevent dangling references.
&i32
a reference
&'a i32
a reference with an explicit lifetime
&'a mut i32
a mutable reference with an explicit lifetime
fn main() { let _s: &'static str = "I have a static lifetime."; }
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
#![allow(dead_code)] 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
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.
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.
#![allow(unused_imports, dead_code)] // For code from an external crate, the absolute path begins with the crate name // Here, the standard `std` library use std::collections::HashMap; // With the following, `File` without prefix is available in the scope use std::fs::File; // Glob - all public objects in `collections` are now in scope use std::collections::*; // Combine multiple `use` lines together with { } use std::{cmp::Ordering, fmt}; // The following is equivalent to `use std::io; use std::io::Write;` use std::io::{self, Write}; mod utils { pub fn insert_use() {} } // Absolute path - for code from the current crate, it starts with the literal crate. use crate::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 crate::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
#![allow(dead_code)] 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)); }
#![allow(dead_code)] #![allow(clippy::match_single_binding)] // 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,}
.
fn main() { let config_max = Some(3u8); if let Some(max) = config_max { // <-- if let println!("The maximum is configured to be {}", max); } }
fn main() { let mut stack = Vec::new(); stack.push(1); stack.push(2); stack.push(3); while let Some(top) = stack.pop() { // <-- while let println!("{}", top); } }
See Also
Closures
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
#![allow(dead_code)] // 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")); }
Fn
: the closure uses the captured value by reference (&T
)FnMut
: the closure uses the captured value by mutable reference (&mut T
)FnOnce
: the closure uses the captured value by value (T
)
Functions may also be used as arguments.
Iterators
#![allow(clippy::useless_vec)] fn main() { let vec1 = vec![1, 2, 3]; let vec2 = vec![4, 5, 6]; // `iter()` for vecs yields `&i32`. Destructure to `i32`. `iter()` // only borrows `vec1` and its elements, so they can be used again println!("2 in vec1: {}", vec1.iter().any(|&x| x == 2)); // `into_iter()` for vecs yields `i32`. No destructuring required. // `into_iter()` does move `vec2` and its elements, so they cannot be // used again println!("2 in vec2: {}", vec2.into_iter().any(|x| x == 2)); }
See Also
Macros
The Little Book of Rust Macros⮳
#![allow(clippy::useless_vec)] 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; }; } trait T { const_maker! {i32, 7} } // Macro calls within macros. macro_rules! _example { () => { println!("Macro call in a macro!") }; } // Outer macro `example` is expanded, then inner macro `println` is // expanded. example!();
Key crates
Paste provides a flexible way to paste together identifiers in a macro, including using pasted identifiers to define new items.
proc-macro2⮳ bring proc-macro-like functionality to other contexts like build.rs and main.rs and makes procedural macros unit testable.
Syn is a parsing library for parsing a stream of Rust tokens into a syntax tree of Rust source code.
Quote provides the quote!
macro for turning Rust syntax tree data structures into tokens of source code.
Tools
See also
Standard Library
Option
Rust has no null
. Instead, use Option⮳:
#![allow(unused)] enum Option<T> { None, Some(T), } fn main() {}
Every Option
is either Some
and contains a value, or None
, and does not.
fn main() { let _some_number = Some(5); let absent_number: Option<i32> = None; println!("{:?}", absent_number); }
It is often used with match
, if let
, or while let
:
fn bake_cake(sprinkles: Option<&str>) -> String { let mut cake = String::from("A delicious cake 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
as_ref
converts from&Option<T>
toOption<&T>
as_mut
converts from&mut Option<T>
toOption<&mut T>
as_deref
converts from&Option<T>
toOption<&T::Target>
as_deref_mut
converts from&mut Option<T>
toOption<&mut T::Target>
Extracting the value contained in Option
These methods extract the contained value in an Option<T>
when it is the Some variant.
If the Option
is None:
expect
panics with a provided custom messageunwrap
panics with a generic messageunwrap_or
returns the provided default valueunwrap_or_default
returns the default value of the type T (which must implement theDefault
trait)unwrap_or_else
returns the result of evaluating the provided function
Combinators
#![allow(clippy::bind_instead_of_map)] 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"), } }
Vectors
Vectors can only store values that are the same type.
#![allow(clippy::vec_init_then_push)] 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
All of the 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
fn main() { // `String` is Unicode, not ASCII let mut s1 = String::from("hello"); s1.push_str(", world!"); // `String` can be mutated s1.clear(); // Empties the String, making it equal to "" // Alternative initialization from string literals // `to_string` is available on any type that implements // the Display trait let s2 = "initial contents".to_string(); // Concatenation: note s1 has been moved here and can no longer // be used afterwards let s3 = s1 + &s2; // ERROR let s = format!("{s1}-{s2}-{s3}"); // String slice - contains the first 4 bytes of the string. let _s: &str = &s3[0..4]; // Caution: If we were to try to slice only part of a unicode // character’s bytes, Rust would panic at runtime. // Iteration for c in "Зд".chars() { println!("{c}"); } for b in "Зд".bytes() { println!("{b}"); } }
Placeholders
fn main() { let x = 5; let y = 10; println!("x = {x} and y + 2 = {}", y + 2); }
Use {:?}
to use the Debug
output format (annotate type with #[derive(Debug)]
) or {:#?}
for pretty print.
Also use dbg!(&rect1);
for debug output (returns ownership of the expression’s value).
String concatenation
Here are several common methods to concatenate Strings:
#![allow(clippy::vec_init_then_push)] #[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(); #[allow(clippy::useless_vec)] 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
Smart Pointers
Rc<T>
enables multiple owners of the same data;Box<T>
andRefCell<T>
have single owners.Box<T>
allows immutable or mutable borrows checked at compile time;Rc<T>
allows only immutable borrows checked at compile time;RefCell<T>
allows immutable or mutable borrows checked at runtime.- Because
RefCell<T>
allows mutable borrows checked at runtime, you can mutate the value inside theRefCell<T>
even when theRefCell<T>
is immutable.
Box
Box<T>
allow you to store data on the heap rather than the stack. What remains on the stack is the pointer to the heap data.
The Box<T>
type is a smart pointer because it implements the Deref trait, which allows Box<T>
values to be treated like references.
Implementing the Deref trait allows you to customize the behavior of the dereference operator *
.
Use when
- you have a type whose size can’t be known at compile time
- you want to own a value and you care only that it’s a type that implements a particular trait rather than being of a specific type.
Rc
The Rc<T>
type keeps track of the number of references to data on the heap so that data can have multiple owners.
RefCell
The RefCell<T>
type with its interior mutability gives us a type that we can use when we need an immutable type but need to change an inner value of that type; it also enforces the borrowing rules at runtime instead of at compile time.
Key crates
Consult the following sites for crate recommendations:
- Blessed.rs⮳
- lib.rs⮳
- lib.rs stats⮳
- The Rust community’s official crate registry: crates.io⮳
- Crates.io's most (recent) downloads⮳
or older resources, such as:
In this section
See also
Regex Crate
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()); }
Serialization
See also
Time and Date
Cross-cutting concerns
- Automatic derivation
- Error handling
- Error customization
- Configuration
- Logging / tracing
- Database access
- Testing
- Lazy initialization
- Performance
- Documentation
Automatic trait derivation
The derive
attribute generates code that will implement a trait with its own default implementation on the type you’ve annotated with the derive syntax.
// on structs #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash, Default)] struct S(i32); fn main() { println!("{:?}", S(0)); println!("{}", S(1) == S(1)); }
You can use the cargo-expand
utility to see the exact code that is generated for your specific type.
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(fmt = "int: {}", _0)]
Int(i32),
Uint(u32),
#[display(fmt = "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");
}
Error Handling
Irrecoverable panics
fn main() { panic!("crash and burn"); }
Recoverable errors with Result
use std::fs::File; 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 = File::open("hello.txt").unwrap(); }
unwrap_or_else
use std::fs::File; use std::io::ErrorKind; fn main() { let _greeting_file = File::open("hello.txt").unwrap_or_else(|error| { if error.kind() == ErrorKind::NotFound { File::create("hello.txt").unwrap_or_else(|error| { panic!("Problem creating the file: {:?}", error); }) } else { panic!("Problem opening the file: {:?}", error); } }); }
A Shortcut for Propagating Errors: the ? Operator
use std::fs::File; use std::io; use std::io::Read; fn read_username_from_file() -> Result<String, io::Error> { let mut username_file = File::open("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 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>;
Custom Errors
Use Anyhow
if you don't care what error type your functions return, you just want it to be easy. This is common in application code.
Use thiserror
if you are a library that wants to design your own dedicated error type(s) so that on failures the caller gets exactly the information that you choose.
Anyhow
Use Result<T, anyhow::Error>
or equivalently anyhow::Result<T>
as the return type of any fallible function.
use anyhow::Context; use anyhow::Result; fn do_something() -> Result<()> { Err(anyhow::Error::msg("Some Error")) } fn main() -> anyhow::Result<()> { // ... do_something().context("Failed to do the important thing")?; // Provide context let _content = std::fs::read("/notafile.txt") .with_context(|| "Failed to read instrs from file".to_string())?; Ok(()) }
Anyhow works with any error type that has an impl of std::error::Error
, including ones defined in your crate e.g. using thiserror
.
thisError
thisError⮳ provides a convenient derive
macro for the standard library’s std::error::Error
trait.
use thiserror::Error; #[derive(Error, Debug)] pub enum DataStoreError { // A Display impl is generated for your error if you provide // #[error("...")] messages on the struct or each variant of your enum #[error("data store disconnected")] Disconnect(#[from] std::io::Error), /* A From impl is generated for * each variant containing * a #[from] attribute. */ #[error("the data for key `{0}` is not available")] Redaction(String), #[error("invalid header (expected {expected:?}, found {found:?})")] InvalidHeader { expected: String, found: String }, #[error("unknown data store error")] Unknown, #[error(transparent)] // forward the source and Display methods straight through to an // underlying error without adding an additional message. Other(#[from] anyhow::Error), } fn main() -> Result<(), Box<dyn std::error::Error>> { Err(DataStoreError::Unknown)?; Ok(()) }
The #[error(...)]
messages support a shorthand for interpolating fields from the error.
#[error("{var}")] ⟶ write!("{}", self.var)
#[error("{0}")] ⟶ write!("{}", self.0)
#[error("{var:?}")] ⟶ write!("{:?}", self.var)
#[error("{0:?}")] ⟶ write!("{:?}", self.0)
use thiserror::Error; #[derive(Error, Debug)] pub struct MyError { msg: String, // The Error trait’s source() method is implemented to return whichever // field has a #[source] attribute or is named source, if any. This is // for identifying the underlying lower level error that caused your // error. #[from] implies #[source]. Any error type that implements // `std::error::Error` or dereferences to `dyn std::error::Error` will work // as a source. #[source] source: std::io::Error, // Automatically detected to implement provide() // backtrace: std::backtrace::Backtrace, } impl std::fmt::Display for MyError { fn fmt( &self, f: &mut std::fmt::Formatter<'_>, ) -> Result<(), std::fmt::Error> { write!(f, "{}", self.msg) } } fn example() -> Result<(), Box<dyn std::error::Error>> { let io_error = std::io::Error::new(std::io::ErrorKind::Other, "oh no!"); Err(Box::new(MyError { msg: "Error message".to_string(), source: io_error, })) } fn main() { match example() { Ok(_) => { println!("Got OK"); } Err(err) => { println!("Got {}", err); } } }
Miette
(lib.rs) prints fancy diagnostics upon error.
// 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 URK 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,
// 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 just 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(())
}
mod mylib;
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(())
}
See also
Do not use Error Chain⮳, which is deprecated.
Configuration
Environment variables
use std::env; use std::error::Error; fn main() -> Result<(), Box<dyn Error>> { // 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(()) }
To retrieve a single environment variable,
use std::env; fn env_extract() -> String { let log_env_var = env::var("RUST_LOG").unwrap_or_else(|_| "debug".into()); println!("RUST_LOG: {log_env_var}"); let user_env_var = env::var("USER").expect("$USER is not set"); println!("USER: {user_env_var}"); // Inspect an environment variable at compile-time. // Uncomment to test. // let shell = env!("SHELL", "$SHELL is not set"); let optional_value = option_env!("SHELL"); optional_value.unwrap_or("no shell set").to_string() } fn main() { println!("SHELL: {}", env_extract()); }
Working with environment variables in Rust⮳
Envy
Envy can deserialize environment variables into typesafe struct.
[dependencies]
envy = "0.4"
serde = { version = "1.0", features = ["derive"] }
#![allow(dead_code)]
use serde::Deserialize;
#[derive(Deserialize, Debug)]
struct Configuration {
port: u16,
items_per_page: u16,
}
fn main() {
let c = envy::from_env::<Configuration>()
.expect("Please provide PORT and ITEMS_PER_PAGE env vars");
let c2 = envy::prefixed("MY_APP__")
.from_env::<Configuration>()
.expect(
"Please provide MY_APP__PORT and MY_APP__ITEMS_PER_PAGE env vars",
);
println!("c: {:?} c2: {:?}", c, c2);
}
Config
Config
is a layered configuration system for Rust applications.
It reads from JSON, TOML, YAML, INI, RON, JSON5 files.
Confy
use serde::Deserialize; use serde::Serialize; #[derive(Serialize, Deserialize)] struct MyConfig { version: u8, api_key: String, } /// `MyConfig` implements `Default` impl ::std::default::Default for MyConfig { fn default() -> Self { Self { version: 0, api_key: "".into(), } } } fn main() -> Result<(), confy::ConfyError> { let _cfg: MyConfig = confy::load("my-app-name", None)?; // confy::store("my-app-name", None, cfg)?; Ok(()) }
See also
Logs
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; fn main() { event!(Level::INFO, "something happened"); error!("!"); warn!("!"); info!("!"); debug!("!"); trace!("!"); 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); // ?: `my_struct` will be recorded // using its `fmt::Debug` implementation. let my_struct = S; event!(Level::TRACE, greeting = ?my_struct); } #[derive(Debug)] struct S;
Spans
use tracing::span; use tracing::Level; fn main() { let span = span!(Level::TRACE, "my_span"); // `enter` returns a RAII guard which, when dropped, exits the span. // This indicates that we are in the span // for the current lexical scope. { let _guard = span.enter(); // 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 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; }
OpenTelemetry
OpenTelemetry Rust documentation⮳
See also
Databases and ORMs
Sqlx
Sqlx is the Rust SQL Toolkit. An async, pure Rust SQL crate featuring compile-time checked queries without a DSL. Supports PostgreSQL, MySQL, SQLite, and MSSQL.
SeaORM
Diesel
See also
Tests
cargo test
to run all tests.
cargo test test_prefix
to run all tests that start with the provided prefix.
cargo test -- --show-output
to show output (println!) that is otherwise captured during tests.
#![allow(dead_code)] // Put unit tests in the same file than the main code #[cfg(test)] // only for unit tests mod tests { // Access to all objects in the parent module, // which contains the main code use super::*; // Test functions must be free, monomorphic functions that take no // arguments, and commonly return () or Result<T, E> where T: // Termination, E: Debug #[test] fn larger_can_hold_smaller() { let larger = Rectangle { width: 8, height: 7, }; let smaller = Rectangle { width: 5, height: 1, }; assert!(larger.can_hold(&smaller)); // or assert_eq!(result, some_const); // or assert_ne! } // This test passes if the code inside the function panics; // It fails if the code inside the function doesn’t panic. #[test] #[should_panic] fn another() { panic!("Make this test fail"); } // With Result #[test] fn it_works() -> Result<(), String> { if 2 + 2 == 4 { Ok(()) // Pass if OK } else { Err(String::from("two plus two does not equal four")) } } #[test] #[ignore] fn expensive_test() { // Code that takes an hour to run } } struct Rectangle { width: u32, height: u32, } impl Rectangle { fn can_hold(&self, _another: &Rectangle) -> bool { true } } fn main() {}
Custom message
#[test] #[should_panic] fn custom_message() { let result = "Carl"; assert!( result.contains("Carol"), "Greeting did not contain name, value was `{}`", result ); } fn main() {}
See Also
cargo-nextest⮳: cargo nextest run; cargo test --doc
Lazy Init
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()); }
Older library
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 Lazy<T>
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()); }
See also
Performance
Links
Rust Performance Book (GitHub)⮳
Incremental Computation
Salsa (GitHub)⮳ is a framework for on-demand, incrementalized computation.
Documentation
docs.rs⮳: open-source documentation host for Rust crates.
Documenting your code
- Add documentation comments to your code.
/// This is a doc comment. It is equivalent to the next line. #[doc = r" This is a doc comment."] fn main() {}
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>
orcargo doc --open
to create a new directory,doc
(ortarget/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() {}
- 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() {}
Badges
Concurrency
This section covers concurrent programming, specifically parallel programming and async 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: Independent units of execution that can be spawned using e.g.
std::thread::spawn
. - Mutexes: Protect shared data from race conditions using e.g.
std::sync::Mutex
. - Channels: Allow threads to communicate and exchange data using e.g.
std::sync::mpsc
.
Here are the topics we’ll cover:
Asynchronous programming
- 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.
- Multithreaded async programming also requires careful synchronization to prevent data races.
Key constructs in Rust:
async
/await
keywordsFuture
s
Here are the topics we’ll cover:
See Also
Multithreading
Spawn, join
use std::thread; use std::time::Duration; fn main() { let thread_one = thread::spawn(|| { for i in 1..10 { println!("hi number {} from the spawned thread!", i); thread::sleep(Duration::from_millis(1)); } }); let thread_two = thread::spawn(|| { /* ... */ }); // more stufff // Wait for both threads to complete. thread_one.join().expect("thread one panicked"); thread_two.join().expect("thread two panicked"); }
When the main thread of a Rust program completes, all spawned threads are shut down, whether or not they have finished running.
Scoped threads
use std::error::Error; use std::path::Path; use std::sync::mpsc; use std::thread; // Our error type needs to be `Send` to be used in a channel fn read_contents<T: AsRef<Path>>( file: T, ) -> Result<String, Box<dyn Error + Send>> { Ok(file.as_ref().to_string_lossy().into_owned()) } fn main() { // To share state between threads, consider using a channel let (tx, rx) = mpsc::channel(); thread::scope(|scope| { // Creates a “fork-join” scope let tx2 = tx.clone(); scope.spawn(move || { println!("hello from the first scoped thread"); let contents = read_contents("foo.txt"); tx.send(contents).unwrap(); }); scope.spawn(move || { println!("hello from the second scoped thread"); let contents = read_contents("bar.txt"); tx2.send(contents).unwrap(); }); }); // No join; spawned threads get joined automatically once the scope // ends! // Receive messages from the channel println!("hello from the main thread"); for received in rx { println!("Got: {:?}", received); } }
Rayon - parallel processing
Parallel iteration
Convert .iter()
or iter_mut()
or into_iter()
into par_iter()
or par_iter_mut()
or into_par_iter()
to execute in parallel.
use rayon::prelude::*; fn sum_of_squares(input: &[i32]) -> i32 { input.par_iter().map(|i| i * i).sum() } fn increment_all(input: &mut [i32]) { input.par_iter_mut().for_each(|p| *p += 1); } fn main() { let mut v = [1, 2, 3]; increment_all(&mut v[..]); println!("{}", sum_of_squares(&v[..])); }
Parallel sorting
use rayon::prelude::*; fn main() { let mut v = [-5, 4, 1, -3, 2]; v.par_sort(); println!("{:#?}", v); }
Custom parallel tasks
Rayon implements join⮳, scope⮳, 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
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
use std::sync::mpsc; use std::thread; use std::time::Duration; fn main() { let (tx, rx) = mpsc::channel(); let tx2 = tx.clone(); thread::spawn(move || { let vals = vec![String::from("hi"), String::from("hi again")]; for val in vals { tx.send(val).unwrap(); thread::sleep(Duration::from_secs(1)); } }); thread::spawn(move || { let vals = vec![String::from("more"), String::from("messages")]; for val in vals { tx2.send(val).unwrap(); thread::sleep(Duration::from_secs(1)); } }); while let Ok(msg) = rx.recv() { println!("{msg}"); } }
Crossbeam_channel
Multi-producer multi-consumer channels for message passing.
use std::thread; use crossbeam_channel::unbounded; use crossbeam_channel::RecvError; use crossbeam_channel::TryRecvError; fn main() { // Create a channel of unbounded capacity. let (s1, r1) = unbounded(); // Alternatively, create a channel that can hold at most n messages at // a time. let (s1, r1) = bounded(5); // Senders and receivers can be cloned to use them to multiple // threads. cloning only creates a new handle to the same sending // or receiving side. It does not create a separate stream of // messages in any way let s2 = s1.clone(); // Send a message into the channel. // Note that the cloned sender is moved into the thread. thread::spawn(move || s2.send("Hi!").unwrap()); // Blocks until receiving the message from the channel. assert_eq!(r1.recv(), Ok("Hi!")); // Try receiving a message without blocking. // The channel is now empty assert_eq!(r1.try_recv(), Err(TryRecvError::Empty)); s1.send("0").unwrap(); // Receive all remaining messages currently in the channel // (non-blocking). let v: Vec<_> = r1.try_iter().collect(); println!("{:?}", v); // When all senders or all receivers associated with a channel get // dropped, the channel becomes disconnected. s1.send("1").unwrap(); drop(s1); // No more messages can be sent... // ERROR s1.send("2").unwrap(); // .. but any remaining messages can still be received. println!("{:?}", r1.iter().collect::<Vec<_>>()); // Note that the call to `collect` would block if the channel were not // disconnected. // There are no more messages in the channel. assert!(r1.is_empty()); // After disconnection, calling `r1.recv()` does not block // Instead, `Err(RecvError)` is returned immediately. assert_eq!(r1.recv(), Err(RecvError)); }
Example using specialized channels for tickers and timeout
use std::time::Duration; use std::time::Instant; use crossbeam_channel::after; use crossbeam_channel::select; use crossbeam_channel::tick; fn main() { let start = Instant::now(); // channel that delivers messages periodically. let ticker = tick(Duration::from_millis(50)); // channel that delivers a single message after a certain duration of // time. let timeout = after(Duration::from_secs(1)); loop { // `select` wait until any one of the channels becomes ready and // execute it. select! { recv(ticker) -> _ => println!("elapsed: {:?}", start.elapsed()), recv(timeout) -> _ => break, // or use: default(Duration::from_millis(1000)) => break, } } }
See also
Shared-State Concurrency
Channels are similar to single ownership, because once you transfer a value down a channel, you should no longer use that value. Shared memory concurrency is like multiple ownership: multiple threads can access the same memory location at the same time.
The Rust standard library provides smart pointer types, such as Mutex<T>
and Arc<T>
, that are safe to use in concurrent contexts.
Mutex
Allow access to data from one thread at a time.
use std::sync::Arc; use std::sync::Mutex; use std::thread; fn main() { // We wrap Mutex in Arc to allow for multiple owners. // Arc<T> is safe to use in concurrent situations. let counter = Arc::new(Mutex::new(0)); let mut handles = vec![]; for _ in 0..10 { // `clone` is somewhat a misnomer; it creates another pointer to the // same Mutex, increasing the strong reference count. let counter = Arc::clone(&counter); let handle = thread::spawn( move || { let mut num = counter.lock().unwrap(); *num += 1; }, /* releases the lock automatically when the MutexGuard * goes out of scope. */ ); handles.push(handle); } for handle in handles { handle.join().unwrap(); } println!("Result: {}", *counter.lock().unwrap()); }
Parking Lot
Parking Lot⮳ provides implementations of Mutex
, RwLock
, Condvar
and Once
that are smaller, faster and more flexible than those in the Rust standard library. It also provides a ReentrantMutex
type.
std::sync::Mutex
works fine, but Parking Lot
is faster.
use parking_lot::Once; static START: Once = Once::new(); fn main() { // run a one-time initialization START.call_once(|| { // run initialization here }); }
use parking_lot::RwLock; fn main() { let lock = RwLock::new(5); // many reader locks can be held at once { let r1 = lock.read(); let r2 = lock.read(); assert_eq!(*r1, 5); assert_eq!(*r2, 5); } // read locks are dropped at this point // only one write lock may be held, however { let mut w = lock.write(); *w += 1; assert_eq!(*w, 6); } // write lock is dropped here }
Atomics
Atomic types in std::sync::atomic⮳ provide primitive shared-memory communication between threads, and are the building blocks of other concurrent types. It defines atomic versions of a select number of primitive types, including AtomicBool
, AtomicIsize
, AtomicUsize
, AtomicI8
, 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 Arc
(an atomically-reference-counted shared pointer).
crossbeam⮳ also offers AtomicCell
, a thread-safe mutable memory location. This type is equivalent to 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); }
Concurrent Data Structures
Dashmap
Dashmap
is an implementation of a concurrent associative array/hashmap in Rust.
DashMap
tries to be a direct replacement for RwLock<HashMap<K, V>>
.
use std::sync::Arc;
use std::thread;
use dashmap::DashMap;
fn main() {
// Create a shared DashMap with an Arc
let map: Arc<DashMap<&str, i32, _>> = Arc::new(DashMap::new());
// or use: DashMap::with_capacity(20)
// Create multiple threads
let mut threads = Vec::new();
for i in 0..4 {
let map_clone = map.clone();
let thread_id = i;
threads.push(thread::spawn(move || {
// Access and modify the map from each thread
match thread_id {
0 => {
map_clone.insert("key1", thread_id);
println!("Thread {} inserted key1", thread_id);
}
1 => {
map_clone.insert("key2", thread_id);
println!("Thread {} inserted key2", thread_id);
}
2 => {
if let Some(value) = map_clone.get("key1") {
println!("Thread {} read key1: {}", thread_id, *value);
} else {
println!("Thread {} couldn't find key1", thread_id);
}
}
3 => {
if let Some(mut value) = map_clone.get_mut("key2") {
*value += 10;
println!(
"Thread {} incremented key2 value to {}",
thread_id, *value
);
} else {
println!("Thread {} couldn't find key2", thread_id);
}
}
_ => panic!("Unknown thread ID"),
}
}));
}
// Wait for all threads to finish
for thread in threads {
thread.join().unwrap();
}
assert_eq!(map.remove("key1").unwrap().1, 0); // returns Option<(K, V)>
assert!(map.contains_key("key2"));
map.remove_if("key2", |_, val| *val == 11);
// Access the final state of the map from the main thread
println!("final count: {}", map.iter().count());
}
Bounded Multi-producer Multi-consumer Queue
use crossbeam_queue::ArrayQueue; fn main() { let q = ArrayQueue::new(2); assert_eq!(q.push('a'), Ok(())); assert_eq!(q.push('b'), Ok(())); assert_eq!(q.push('c'), Err('c')); assert_eq!(q.pop(), Some('a')); }
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
Asynchronous Programming in Rust (book)⮳
Basic Example
use std::future::Future; struct SomeStruct; // Most often, we will use async functions. // Rust transforms the `async fn` at compile time into a state machine // that _implicitly_ returns a `Future`. A future represents an // asynchronous computation that might not have finished yet. async fn first_task() -> SomeStruct { // ... SomeStruct } async fn second_task_1(_s: &SomeStruct) { /* ... */ } // `async fn` is really syntaxic sugar for a function... #[allow(clippy::manual_async_fn)] fn second_task_2() -> impl Future<Output = ()> { // ...that contains an `async` block. async {} // returns `Future<Output = ()>` } async fn do_something() { // Use `.await` to start executing the future. let s = first_task().await; // `await` yields control back to the executor, which may decide to do // other work if the task is not ready, then come back here. // `join!` is like `.await` but can wait for multiple futures // concurrently, returning when all branches complete. let f1 = second_task_1(&s); let f2 = second_task_2(); futures::join!(f1, f2); // or tokio::join! } // We replace `fn main()` by `async fn main()` and declare which // executor runtime we'll use - in this case, Tokio. The runtime crate // must be added to `Cargo.toml`: `tokio = { version = "1", features = // ["full"] }` Technically, the #[tokio::main] attribute is a macro // that transforms it into a synchronous fn main() that initializes a // runtime instance and executes the async main function. #[tokio::main] async fn main() { do_something().await; // note: `await` must be called or nothing is executing - Futures // are lazy }
As any form of cooperative multitasking, a future that spends a long time without reaching an await
"blocks the thread", which may prevent other tasks from running.
Differences with other languages
Rust's implementation of async
differs from most languages in a few ways:
- Rust's
async
operations are lazy. Futures are inert in Rust and only make progress only when polled. The executor calls thepoll
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 multithreaded runtimes are available.
Which crate provides what?
- The
async
/await
syntaxic sugar is supported directly by the Rust compiler. - The most fundamental traits, types, and functions, such as the
Future
trait, are provided by the standard library. - Many utility types, macros and functions are provided by the
futures
crate. They can be used in any async Rust application. - Execution of async code, IO and task spawning are provided by "async runtimes", such as
Tokio
andasync-std
. Most async applications, and some async crates, depend on a specific runtime.
Async runtimes
In most cases, prefer the Tokio runtime - see The State of Async Rust: Runtimes⮳.
Alternatives to the Tokio async ecosystem include:
- async-std⮳: async version of the Rust standard library. No longer maintained?
- Smol⮳
- Embassy⮳ for embedded systems.
- Mio⮳ is a fast, low-level I/O library for Rust focusing on non-blocking APIs and event notification for building high performance I/O apps with as little overhead as possible over the OS abstractions. It is part of the Tokio ecosystem.
Async traits
As of Rust 1.75, it is possible to have async
functions in traits:
struct MyHealthChecker; trait HealthCheck { async fn check(&mut self) -> bool; // <- async fn defined in a Trait } impl HealthCheck for MyHealthChecker { async fn check(&mut self) -> bool { // async fn implementation in the associated impl block do_async_op().await } } async fn do_health_check(mut hc: impl HealthCheck) { if !hc.check().await { // use as normal log_health_check_failure().await; } } async fn do_async_op() -> bool { true } async fn log_health_check_failure() {} #[tokio::main] async fn main() { let hc = MyHealthChecker; do_health_check(hc).await; }
Stabilizing async fn in traits in 2023⮳
This is in turn enabled by return-position impl Trait
in traits, since async fn
is sugar for functions that return -> impl Future
.
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 (github).
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
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.
Key links
- Tokio⮳
- Tokio glossary⮳
- Tokio tutorial⮳
- Tokio examples⮳
- Tokio mini-Redis example⮳
- Template for a tokio-rs app with logging & command line argument parser: rust-tokio-template⮳
Graceful shutdown
Example from 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;
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() -> std::result::Result<(), Box<dyn std::error::Error>> {
// Init logging
tracing_subscriber::fmt()
.with_max_level(tracing::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 channels.
Tokio's sync
module provides channels that work well with async code.
OneShot
oneshot
sends a single value from a single producer to a single consumer.
This channel is usually used to send the result of a computation to a waiter.
use tokio::sync::oneshot; async fn some_computation(input: u32) -> String { format!("the result of computation is {}", input) } async fn one_shot() { let (tx, rx) = oneshot::channel(); tokio::spawn(async move { let res = some_computation(0).await; tx.send(res).unwrap(); // Alternatively, return the value via the joinhandle returned // by `spawn` }); // Do other work while the computation is happening in the background // Wait for the computation result let res = rx.await.unwrap(); println!("{}", res); } #[tokio::main] async fn main() { one_shot().await; }
Another example:
use std::time::Duration; use tokio::sync::oneshot; async fn download_file() -> Result<String, std::io::Error> { // Simulate downloading a file let filename = "data.txt"; tokio::time::sleep(Duration::from_secs(2)).await; println!("Downloaded file: {}", filename); Ok(filename.to_owned()) } async fn process_file(filename: String) { // Simulate processing the downloaded file println!("Processing file: {}", filename); tokio::time::sleep(Duration::from_secs(1)).await; println!("Finished processing file."); } async fn async_main() -> Result<(), Box<dyn std::error::Error>> { let (sender, receiver) = oneshot::channel(); // Spawn the download task tokio::spawn(async move { let filename = download_file().await?; sender.send(filename).expect("Failed to send filename"); Ok::<(), std::io::Error>(()) }); // Wait for the downloaded filename from the receiver let filename = receiver.await?; // Spawn the processing task with the filename tokio::spawn(async move { process_file(filename).await; }); Ok(()) } fn main() { let rt = tokio::runtime::Runtime::new().unwrap(); rt.block_on(async { async_main().await }).unwrap(); }
Multiple Producer, Single Consumer
use tokio::sync::mpsc; async fn some_computation(input: u32) -> String { format!("the result of computation is {}", input) } pub async fn multi_producer_single_receiver() { let (tx, mut rx) = mpsc::channel(100); tokio::spawn(async move { for i in 1..=10 { let res = some_computation(i).await; tx.send(res).await.unwrap(); } }); while let Some(res) = rx.recv().await { println!("{}", res); } } #[tokio::main] async fn main() { multi_producer_single_receiver().await; }
See also
Streams
Futures are about a single value that will eventually be produced, but many event sources naturally produce a 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 map
, filter
, and fold
, and their early-exit-on-error cousins try_map
, try_filter
, and try_fold
.
To process multiple items from a stream concurrently, use the for_each_concurrent
and try_for_each_concurrent
methods:
use futures::StreamExt;
use tokio::fs::File;
use tokio::io;
type Result = std::result::Result<(), Box<dyn std::error::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 = ["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 Tokio async-stream (GitHub)⮳ .
Futures crate
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
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.
#![allow(dead_code)] use futures::{ future::FutureExt, // for `.fuse()` pin_mut, select, }; async fn task_one() { // ... } async fn task_two() { // ... } async fn race_tasks() { let t1 = task_one().fuse(); let t2 = task_two().fuse(); pin_mut!(t1, t2); select! { () = t1 => println!("task one completed first"), () = t2 => println!("task two completed first"), } } #[tokio::main] async fn main() { race_tasks().await; }
Joining futures
use futures::join; async fn foo(i: u32) -> u32 { i } #[tokio::main] async fn main() { // The `join!` macro polls multiple futures simultaneously, returning // a tuple of all results once complete. assert_eq!(join!(foo(1), foo(2)), (1, 2)); // `join!` is variadic, so you can pass any number of futures // `join_all` create a future which represents a collection of the // outputs of the futures given. let futures = vec![foo(1), foo(2), foo(3)]; assert_eq!(futures::future::join_all(futures).await, [1, 2, 3]); }
Map, then, either, flatten
The futures
crate provides an extension trait that provides a variety of convenient adapters.
#![allow(clippy::async_yields_async)] use futures::future::FutureExt; #[tokio::main] async fn main() -> Result<(), Box<dyn std::error::Error>> { 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
Mixing Async and Blocking Code
Calling blocking code from async code
- Async code should never spend a long time without reaching an
.await
. - Don't carelessly mix async code and synchronous, blocking calls like
std::thread::sleep(Duration::from_secs(N));
- If you have to block the thread because of expensive CPU-bound computation, call to a synchronous IO API, use the
spawn_blocking
function, userayon
, or spawn a dedicated thread.
See Async: What is blocking? blog post⮳.
Tokio spawn_blocking
Use spawn_blocking
⮳ to run a small portion of synchronous code.
#[tokio::main] async fn main() { // This is running on Tokio. We may not block here. let blocking_task = tokio::task::spawn_blocking(|| { // This is running on a thread where blocking is fine. println!("Inside spawn_blocking"); }); blocking_task.await.unwrap(); }
Using the rayon
crate
use rayon::prelude::*; async fn parallel_sum(nums: Vec<i32>) -> i32 { let (tx, rx) = tokio::sync::oneshot::channel(); // Spawn a task on rayon. rayon::spawn(move || { // Perform an expensive computation on this thread... // ...or compute the sum on multiple rayon threads. let sum = nums.par_iter().sum(); // Send the result back to Tokio. let _ = tx.send(sum); }); // Wait for the rayon task. rx.await.expect("Panic in rayon::spawn") } #[tokio::main] async fn main() { let nums = vec![1; 1024 * 1024]; println!("{}", parallel_sum(nums).await); }
Spawn a dedicated thread
If a blocking operation keeps running forever, you should run it on a dedicated thread.
async fn parallel_sum(nums: Vec<i32>) -> i32 { let (tx, rx) = tokio::sync::oneshot::channel(); // Spawn a task on a dedicate thread. std::thread::spawn(move || { // Perform an expensive computation on this thread... let sum = nums.into_iter().sum(); // Send the result back to Tokio. let _ = tx.send(sum); }); // Wait for the rayon task. rx.await.expect("Panic in rayon::spawn") } #[tokio::main] async fn main() { let nums = vec![1; 1024 * 1024]; println!("{}", parallel_sum(nums).await); }
Call async code from blocking code
In other cases, it may be easier to structure the application as largely synchronous, with smaller or logically distinct asynchronous portions. For instance, a GUI application might want to run the GUI code on the main thread and run a Tokio runtime next to it on another thread.
Futures executor
futures-executor⮳ includes a minimal executor. The block_on
⮳ function is useful if you want to run an async function synchronously in codebase that is mostly synchronous.
async fn do_something() {} fn main() { let future = do_something(); // Futures are lazy - nothing is happening // until driven to completion by .await, block_on... // `block_on` blocks the current thread until the provided future has // run to completion. Other executors provide more complex // behavior, like scheduling multiple futures onto the same // thread. See `Tokio`. futures::executor::block_on(future); // `future` is run and "hello, world!" is printed }
Using the Tokio runtime directly
fn main() { let runtime = tokio::runtime::Builder::new_multi_thread() .worker_threads(1) .enable_all() .build() .unwrap(); let mut handles = Vec::with_capacity(10); for i in 0..10 { handles.push(runtime.spawn(my_bg_task(i))); } // Do something time-consuming while the async background tasks // execute. std::thread::sleep(std::time::Duration::from_millis(750)); println!("Finished time-consuming task."); // Wait for all of them to complete. for handle in handles { // The `spawn` method returns a `JoinHandle`. A `JoinHandle` is // a future, so we can wait for it using `block_on`. runtime.block_on(handle).unwrap(); } } // example async code to excute 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); }
Web
References
Building a crawler in Rust: Design and Associated Types⮳
Axum
Crates.io example source code (using Axum)⮳
Actix
Auth Web Microservice with rust using Actix-Web 4.0 - Complete Tutorial⮳
Practical Rust Web Development - API Rest⮳
Other Web Frameworks
Loco
Rocket
Rust + Rocket RealWorld framework implementation⮳
See also
Building a SaaS with Rust and Next.js⮳
Static Website Generators
Zola⮳
Middleware
Tower
Tower⮳ is a library of modular and reusable components for building robust networking clients and servers.
Tower provides a simple core abstraction, the 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 Layer
trait, is used to compose middleware with Services. A Layer
is a function taking a Service of one type and returning a Service of a different type. The ServiceBuilder
type is used to add middleware to a service by composing it with multiple Layers.
The 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 Service
trait: hyper⮳, tonic (gRPC)⮳.
Building a Tower middleware from scratch⮳
Tower HTTP
Tower HTTP⮳ contains HTTP specific Tower utilities.
#![allow(dead_code)]
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
CORS
Using the Tower ecosystem:
use std::convert::Infallible;
use std::error::Error;
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<(), Box<dyn Error>> {
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(())
}
HTTP clients
Other Domains
- CLI
- WASM
- GUI programming
- Cross-platform development
- Cloud
- Data
- ML
- Games
- GPU programming
- Robotics
- Windows
CLI
Command Line Applications in Rust (book)⮳
Command-line argument parsing
(tutorial) (cookbook) (examples)
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
TUIFY YOUR CLAP CLI APPS AND MAKE THEM MORE INTERACTIVE⮳
WASM
Yew
Example real world app built with Rust + Yew + WebAssembly⮳
GUI
GTK and Tauri are probably the only options which can be described as production-ready without caveats. The Rust native options are usable for simple projects but are all still quite incomplete.
See the relevant section in blessed.rs⮳
Tauri
Tauri⮳ is an app construction toolkit that lets you build software for all major desktop operating systems using web technologies. It is similar to Electron
.
egui
egui⮳ is an easy-to-use immediate mode GUI that runs on both web and native. egui
aims to be the best choice when you want a simple way to create a GUI, or you want to add a GUI to a game engine.
Other GUI frameworks
(github) is a cross-platform GUI library for Rust, inspired by Elm.
(github) is a data-first Rust-native UI design toolkit (experimental).
Cross-platform Applications
Crux
Crux(GitHub)⮳ 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 messaged 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.
Cloud
AWS
Dapr
Dapr⮳ is a portable, event-driven, serverless runtime for building distributed applications across cloud and edge.
Data and ETL
(github) is the official Rust implementation of Apache Arrow
is the Apache Arrow DataFusion
SQL Query Engine.
Machine Learning
Watchmaker (genetic algos in Rust)⮳
Quant
Games
Bevy⮳
GPU Programming
Links
Robotics
Links
Bonsai BT⮳ is a Rust implementation of behavior trees.
Useful tools and libraries
Windows
Links
Tools
- Rust installation
- Crate registries
- IDEs
- Cargo
- Package layout
- Faster linking
- Formatting and linting
- Miri
- Cross-compilation
- Rustup
- Just
- mdBook
- Other tools
Rust Installation
Key Steps
- Install Rustup⮳
On WSL / Unix:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
- Check whether you have Rust installed correctly
rustc --version
cargo --version
- Open the documentation, if needed
rustup doc
- Create a new project
cargo new hello_world
cd hello_world
code . # open VS Code and edit
- Build / run the code.
cargo check
cargo build
cargo run
Crates
In Rust, a library or executable program is called a crate. Crates are compiled using the Rust compiler, rustc
.
Crate Registries
The Rust community’s crate registry: crates.io⮳
Alternative to crates.io: lib.rs⮳
IDEs
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.
Cargo (package manager) and 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 https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html⮳
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.
# Install if needed
cargo install cargo-edit
# Add dependencies to Cargo.toml from the command line
cargo add actix-web@4.0.0
cargo-watch
cargo install cargo-watch
# Runs `cargo check` after every code change
cargo watch -x check
# Run cargo check after code changes.
# If it succeeds, it launches cargo test.
# If tests pass, it launches the application with cargo run.
cargo watch -x check -x test -x run
Formatting
# Install `rustfmt` if needed
rustup component add rustfmt
cargo fmt
# Fails if code is not formatted; use in CD / CI
cargo fmt -- --check
Linting
rustup component add clippy # install if needed
cargo clippy
Mute a warning using the #[allow(clippy::lint_name)]
attributes
Fix
Can automatically fix compiler warnings that have a clear way to correct the problem that’s likely what you want.
cargo fix
Code coverage
Security audit
cargo install cargo-audit
cargo audit
Unused dependencies
or (simpler) Machete⮳
cargo install cargo-machete
cargo machete
Templates
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.
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
- 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.
Faster linking
The Rust compiler spends a lot of time in the "link" step. LLD is much faster at linking than the default Rust linker.
The default linker does a good job, but there are faster alternatives depending on the operating system you are using:
lld
on Windows and Linux, a linker developed by the LLVM project;zld
on MacOS.
To speed up the linking phase you have to install the alternative linker on your machine and add this configuration file to the project:
# .cargo/config.toml
# On Windows
# ```
# cargo install -f cargo-binutils
# rustup component add llvm-tools-preview
# ```
[target.x86_64-pc-windows-msvc]
rustflags = ["-C", "link-arg=-fuse-ld=lld"]
[target.x86_64-pc-windows-gnu]
rustflags = ["-C", "link-arg=-fuse-ld=lld"]
# On Linux:
# - Ubuntu, `sudo apt-get install lld clang`
# - Arch, `sudo pacman -S lld clang`
[target.x86_64-unknown-linux-gnu]
rustflags = ["-C", "linker=clang", "-C", "link-arg=-fuse-ld=lld"]
# On MacOS, `brew install michaeleisel/zld/zld`
[target.x86_64-apple-darwin]
rustflags = ["-C", "link-arg=-fuse-ld=/usr/local/bin/zld"]
[target.aarch64-apple-darwin]
rustflags = ["-C", "link-arg=-fuse-ld=/usr/local/bin/zld"]
cargo-binutils
packages Cargo subcommands to invoke the LLVM tools shipped with the Rust toolchain.
Alternative - Mold linker
mold
is up to 5× faster than lld
, but with a few caveats like limited platform support and occasional stability issues. To install mold, run sudo apt-get install mold clang
in Ubuntu.
You will also need to add the following to your Cargo config at .cargo/config.toml
:
[target.x86_64-unknown-linux-gnu]
linker = "clang"
rustflags = ["-C", "link-arg=-fuse-ld=/usr/bin/mold"]
Reference
Formatting and Linting
Rustfmt
Install with rustup component add rustfmt
rustfmt <filename e.g. lib.rs> <main.rs> ...
# or for the whole project
cargo fmt
Using --check
instructs rustfmt
to exit with an error code if the input is not formatted correctly (useful for CI).
cargo fmt --all -- --check
Configuration
Create a rustfmt.toml
in the project root folder.
For example,
edition = "2021"
version = "Two"
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)]
Miri Interpreter
Miri (GitHub)⮳ 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
Cross-compilation
Cross
cross⮳ builds your Rust project for different target operating systems and architectures.
It requires rustup
and Docker
or Podman
.
cargo install cross --git https://github.com/cross-rs/cross
# Optionally, if you have cargo-binstall, you can install via pre-built binary
cargo binstall cross
cross
has the exact same CLI as Cargo but relies on Docker or Podman.
cross build --target aarch64-unknown-linux-gnu
cross test --target mips64-unknown-linux-gnuabi64
cross run --target aarch64-unknown-linux-gnu
Rustup
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 verion of Rust
rustup update
# Show which toolchain will be used in the current directory
rustup show
# Show which toolchain will be used in the current directory
rustup target list
# Overview of what is installed on your system
rustup toolchain list
# See a list of available and installed components.
rustup component list
See also
Just
just
is a command runner / Make replacement.
Installation in a dev container
FROM mcr.microsoft.com/devcontainers/base:bullseye
# or perhaps mcr.microsoft.com/devcontainers/rust:bullseye if you want rust & cargo
SHELL ["bash", "-c"]
# Prereqs to install Just: https://just.systems/man/en/chapter_4.html
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/chapter_1.html
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!"
mdBook
mdBook⮳: a utility to create modern online books from Markdown files.
cargo install mdbook
cargo install mdbook-hide # optional plugin; many others exist
mdbook serve --open
See also
Other Tools
API search
Deployment
Tools written in Rust
My terminal became more Rusty Community⮳
Bat⮳
Exa⮳
Essential Rust Links
- "The Book": The Rust Programming Language⮳
- Rust By Example⮳
- Rust by Practice⮳
- Rust Cookbook (fork)⮳
- Rust Cookbook (old)⮳
- Rust Design Patterns⮳
- Easy Rust⮳
- The Rustonomicon⮳
Lists of Rust links
- The Rust Starter Pack⮳
- Awesome Rust⮳: a curated list of Rust code and resources.
Learn
- rust-learning⮳: a bunch of links to blog posts, articles, videos, etc for learning Rust
- Learning Rust With Entirely Too Many Linked Lists⮳
- Rust quizz⮳
In this section
Example code
Zero To Production In Rust (book)⮳
Zero To Production In Rust (code)⮳
Rust Cheatsheets
Comparison to other languages
Blogs
Books
Programming Rust, 2nd edition⮳
The Rust Programming Language, 2nd Edition⮳
Companies that use or contribute to Rust
Categories
Crates
Crates mentioned in this book, by alphabetic order.
A
B
C
D
E
F
G - K
L
M
N
O - Q
R
S
T
U
V - Z
Thanks
This reference guide is inspired from online websites, blogs, and documentation, including: