kuniga.me > Docs > Rust Cheatsheet
charf32, f64 - float point typeArrays are of fixed size. For variable size, see Vec.
// Initialize array
let arr: [i32; 3] = [1, 2, 3];
let arr: [i32; 10] = [0; 10];let it:
let v: Option<Self::Item> = it.next()booltrue, falseenum Direction {
N,
E,
S,
W,
}Adding conversion to string:
use std::fmt;
impl fmt::Display for Direction {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let s = match self {
Color::N => "North",
Color::E => "East",
Color::S => "South",
Color::W => "West",
};
write!(f, "{}", s)
}
}
// In code
let s = Direction::N.to_string();i8, i16, i32, i64, u8, u16, u32, u64! is the Rust version of ~
Note that Iterator is not a class but rather a trait that classes implement.
Get the next element:
// returns Split class which implements Iterator
let mut parts = s.split(',');
let e: Option<&str> = parts.next();NOTE: next() is not idempotent.
option<T>// Handle option:
match my_option {
Some(value) => value,
None => 1, // handle null value
}Create Some:
Some(1)Throw if None:
my_option.unwrap()Test if some:
my_option.is_some()Test if none:
my_option.is_none()Get a default value if none:
let v = my_option.unwrap_or(default_value)or if the default is expensive to compute:
let v = my_option.unwrap_or_else(|| get_default_value())Done via tuples. See Tuple.
String - string object&str - string slice&'static str - string literal// Empty string
let mut my_str: String = "".to_owned();
// Other form
let mut my_str = String::from("Hello");
// Length
my_str.len();
// Iterate over its characters
for c in my_str.chars() {
// do something with c
}let concatenated = format!("{}{}", a, b);From literal strings:
let s: String = "hello".to_owned();Convert to int:
let i = s.parse::<i32>().expect("Should be numeric");Convert from int:
let i: i32 = 10;
let s = i.to_string();Convert to Vec<char>:
let cs: Vec<char> = s.chars().collect();Split:
let parts: Vec<&str> = s.split(',').collect();Split into multiple lines. There’s a shortcut for splitting by the character \n:
let parts: Vec<&str> = s.lines().collect();Substring:
s.contains(p);Trim:
let trimmed = s.trim();keywords: record / object / shape
struct Tree {
guess: Vec<i32>,
children: Vec<Tree>,
}let foo = Foo { x: (1, 2), y: 3 };
let Foo { x: (a, b), y } = foo;struct Rectangle {
length: u32,
width: u32,
}
impl Rectangle {
fn area(&self) -> u32 {
self.length * self.width
}
}Type: (T1, T2), e.g. (i32, bool)
Create:
let tup: (i32, String) = (64, "hello")Access:
tup.0 // 64
tup.1 // "hello"or via destructuring:
let (n, s) = &tup;“void” is an empty tuple an in rust is called the unit type. Example:
fn returns_void() -> () {
return ();
}() is also what gets returned if no return is provided:
fn returns_void() -> () {}fn myFun(arg1: i32, arg2: i32) -> i32 {
}Rust doesn’t support default arguments
keywords: lambda
let plus_one = |x: i32| x + 1;let multi_line = |x: i32| {
let mut result: i32 = x;
result += 1;
result
}if n < 0 {
print!("{} is negative", n);
} else if n > 0 {
print!("{} is positive", n);
} else {
print!("{} is zero", n);
}Early-return/continue:
let Some(value) = maybe else {
continue;
}for i in 0..3 {
println!("{}", i);
}See also “Iterating” on different data structures.
iter() borrows the collection, so it’s still valid after the loop. However, each element is also a borrowed reference. There’s a variant iter_mut(), in which the borrowed reference is mutable, so any changes are done inline on the collection.
for item in vec.iter() {
// If vec is Vec<T>, item is a borrowed reference &T
}into_iter() moves the collection into the loop, so it’s not valid afterwards. Each element in the loop is not a reference.
for item in vec.into_iter() {
// If vec is Vec<T>, item is T, and was moved from vec
}
// vec is not valid anymoreIf you wish to mutate the element:
for mut item in vec.into_iter() {
// item was moved from vec
}Keyword: Exceptions
let r: Result<()> = Ok(());
if r.is_ok() {
println!("ok");
}
if r.is_err() {
println!("not ok: {}", r.unwrap_err());
}Result has type Result<T, E> and is union of Ok<T> and Err<E>. Example of a function that returns a Result:
fn maybe(v: i32) -> Result<i32, String> {
if (v == 0) {
return Err("Cannot be 0".to_owned());
}
Ok(v)
}On the caller side:
let x = maybe(v);
match x {
Ok(value) => println!("{}", value),
Err(error) => println!("{}", error),
}To avoid specifying the error type, one can use the Result from the crate anyhow. Importing create in Cargo.toml:
[dependencies]
anyhow = "1.0"Example from before:
use anyhow::{Result, anyhow};
fn maybe(v: i32) -> Result<i32> {
if v == 0 {
return Err(anyhow!("Cannot be 0"));
}
Ok(v)
}The rest is the same. Noting that anyhow::Result<T> is an alias for Result<T, anyhow::Error>. The anyhow:Error is created using the macro anyhow!.
To map Result<T, E> into Result<T>:
use anyhow::{Result, anyhow};
fn my_func() -> Result<T, E>;
my_func()
.map_err(|e| Error::new(e).context("new error"))?;If a function calls functions returning Result it can propagate the results like so:
use anyhow::{Result, anyhow};
...
fn forward(v: i32) -> Result<i32> {
let r = maybe(v);
if r.is_err() {
return r;
}
let x = r.unwrap();
Ok(x + 1)
}There’s a very convenient operator, ?, to avoid this boilerplate:
use anyhow::{Result, anyhow};
...
fn propagate(v: i32) -> Result<i32> {
let x = maybe(v)?;
Ok(x + 1)
}Keyword: rethrow
If some context is needed, we can use with_context():
use anyhow::{Result, anyhow};
...
fn propagate(v: i32) -> Result<i32> {
let x = maybe(v).with_context(||
format!("third_error")
)?;
Ok(x + 1)
}In case of error
third_error
Caused by:
0: original_error
1: secondary_error
BTreeMap is another implementation of a efficient key-value (the other being HashMap). One analogy for C++ is that BTreeMap is std::ordered_map and HashMap is std::unordered_map.
use std::collections::BTreeMap;
// Create empty
let mut my_map = btreemap! {};
// Create initialized
use maplit::btreemap;
let my_map = btreemap! {
"blue" => 1,
"green" => 2,
"red" => 3
};There’s an analogy between BTreeSet and HashSet with BTreeMap and HashMap.
// Create empty
let mut my_map = btreeset![];
// Create initialized
use maplit::btreeset;
let fruits = btreeset![
"apple",
"banana"
]let mut vec = Vec::new();Fixed size, same value:
let mut vec = vec![0; 100];vec.push(1);for item in vec.iter() {
...
}let u = vec![1, 2, 3];
let v: Vec<_> = u.iter().map(f).collect();let u = vec![1, 2, 3];
let v: Vec<_> = u.iter().filter(f).collect();In-place
let mut u = vec![1, 2, 3];
u.retain(f);vec.len()vec.sort_by(|a, b| a.cmp(b))Like in Python, we can do destructured assignment by assuming a fixed length of a vector, but we have to handle the other cases:
let [a, b] = vec.as_slice() else {
panic!();
}Reference: HashMap.
Import name:
use std::collections::HashMap;Type definition:
HashMap<String, String>;Create empty:
let mut my_map = HashMap::new()Create initialized:
let my_map = HashMap::from([
("blue", 1),
("green", 2),
("red", 3),
]);or
use maplit::hashmap;
let my_map = hashmap!{
"blue" => 1,
"green" => 2,
"red" => 3
};Insert:
my_map.insert(
"key_a".to_string(),
"value_a".to_string(),
);Access:
match my_map.get("key_a") {
Some(value) => println!("value={}", value),
None => println!("not found")
}Update
if let Some(value) = my_map.get_mut(key) {
*curr_value = 1;
}All values
for value in my_map.values() {
}All keys
for key in my_map.keys() {
}Use:
use std::collections::HashSet;Create:
let s = HashSet::new();Create initialized:
use maplit::hashset;
let my_map = hashset![1, 2, 3];From vector:
let v = vec![1, 2, 3];
// cloned needed if borrowing v
let s: HashSet<_> = v.iter().cloned().collect();Set intersection:
let i: HashSet<_> = s1.intersection(&s2).cloned().collect();use std::collections::VecDeque;
// Create
let mut queue = VecDeque::new();
// Enqueue
queue.push_back("a");
// Dequeue
if let Some(x) = queue.pop_front() {
// use x
}
// Is empty?
if !queue.is_empty() {
// use queue
}pub structure MyClass {
my_field: bool,
}
impl MyClass {
// Constructor-like
pub fn new() -> MyClass {
return MyClass {
my_field: true,
}
}
// Read Method
pub fn get(&self) -> bool {
self.my_field
}
// Write Method
pub fn set(&mut self, value: bool) {
self.my_field = value;
}
}Keywords: Bypass read-only of &self.
By default you can’t mutate a field inside a method if you pass &self. There’s a way to bypass this for implementation details, by using RefCell, Mutext or Atomic types.
pub structure MyClass {
my_field: bool,
was_called: Arc<AtomicBool>,
}
impl MyClass {
// Constructor-like
pub fn new() -> MyClass {
return MyClass {
my_field: true,
was_called: Arc::new(AtomicBool::new(false))
}
}
// Read Method with internal mutability
pub fn get(&self) -> bool {
self.was_called.store(true, Ordering::Relaxed);
self.my_field
}
}Traits are analogous to interfaces in other languages (though it can have default implementation).
pub trait Summary {
fn summarize(&self) -> String;
}Implement a trait for a structure:
pub struct Article {
pub text: String,
}
impl Summary for Article {
fn summarize(&self) -> String {
self.text
}
}Note: we can’t implement external traits on external types.
pub trait Summary {
fn summarize(&self) -> String {
String::from("(Read more...)")
}
}
impl Summary for Article {}Traits can be used as type constraints:
pub fn notify(item: &impl Summary) {
println!("Breaking news! {}", item.summarize());
}Syntax sugar to:
pub fn notify<T: Summary>(item: &T) {
println!("Breaking news! {}", item.summarize());
}Multiple traits requirement:
pub fn notify(item: &impl Summary + Display) {
...
}or
pub fn notify<T: Summary + Display>(item: &T) {
...
}pub fn get() -> impl Summary {
...
}Note however that get() cannot return different implementations of Summary.
Below, the method my_summary is only implemented for template types implementing Summary, e.g. C<Article>.
struct C<T> {
x: T,
}
impl<T: Display> C<T> {
fn my_display(&self) {
...
}
}A trait can be implemented for a struct conditioned on its type satisfying some other trait:
impl<T: Display> AnotherTrait for T {
...
}If we want to have a pointer to objects of a class implementing a trait MyTrait, we can use dynamic dispatch so that methods are resolved at runtime. Example:
struct A {}
struct B {}
trait MyTrait {
fn method(&self) {}
}
impl MyTrait for A {
fn method(&self) {}
}
impl MyTrait for B {
fn method(&self) {}
}
fn f(ptr: Arc<dyn MyTrait>) {
ptr.method()
}
f(Arc::new(A {}));
f(Arc::new(B {}));use std::io::{self, Read};
let mut input = String::new();
io::stdin().read_to_string(&mut input)
.expect("Failed to read input");use std::env;
// Skip program name
let args: Vec<String> = env::args().skip(1).collect();Vector:
let vec = vec![1, 2, 3, 4, 5];
println!("Vector:\n{:#?}", vec);use tempfile::NamedTempFile;
use std::io::{Write, Read};
let mut tmp_file = NamedTempFile::new()?;
// Write
writeln!(tmp_file, "Hello world!")?;
// Read
let mut contents = String::new();
tmp_file.reopen()?.read_to_string(&mut contents)?;A lot of the math operations are methods on the numerical types.
let b: i32 = 10;
let p10: i32 = b.pow(2 as u32);Note that the exponent has to be positive, since a negative one could change the type of the base to floating point.
let x: f64 = 10.0;
let y: f64 = x.sqrt();Not defined for integer types.
Variable doesn’t need to be mutable if it’s initialized only once:
let x: i32;
if check() {
x = 1;
} else {
x = 2;
}Attributes are in the form #[foo] and placed on top of functions, structs and modules to add metadata to it, e.g.
#[test]
fn test_invalid_argument() {
...
}or
#[derive(Clone, Debug, Eq)]
struct Tree {
...
}pubDeclaration:
mod math {
fn add(a: i32, b: i32) -> i32 {
a + b
}
pub fn sub(a: i32, b: i32) -> i32 {
a - b
}
}Usage:
fn f() {
math::sub(1, 2);
}Modules can be nested but because of visibility being private by default, inner modules must be made explicitly public:
mod outer {
pub mod inner {
pub fn f() {
println!("Hello world");
}
}
}
fn f() {
outer::inner(1, 2);
}Box is analogous to std::unique_ptr in C++.
let x = Box::new(5);Remembering that move happens by default on assignment in Rust:
let y = x; // x is now invalidstd::sync::Arc is analogous to std::shared_ptr in C++. It’s a reference counted pointer to the heap. Creation:
let x = Arc::new(5);Share ownership:
let y = Arc::clone(&x);In Rust we have 3 ways of passing data from one variable to another:
There’s a convention for classes to implement these different modes.
| Mode / semantics | API |
|---|---|
| Copy | to_ |
| Borrow | as_ |
| Move | into_ |
Reference: Rust API Guidelines