This Rust cheat sheet is an exhaustive guide that elegantly unfolds the complexities of the Rust programming language in a structured and digestible format. From the foundational "Hello, World!" example, through the intricacies of types, control flow, functions, and error handling, to advanced concepts of ownership, borrowing, lifetimes, and structs, it caters to both newcomers and seasoned developers. The inclusion of best practices at the end serves as sage advice for writing clean, efficient, and idiomatic Rust code, making this cheat sheet a go-to reference for anyone looking to master Rust programming.
Getting Started
Rust Hello World
fn main() {
println!("Hello, World!");
}
Compiling and Running
$ rustc Hello_World.rs
$ ./Hello_World
Hello, World!
Primitive types
bool |
Boolean (true / false ) |
char |
character |
f32 , f64 |
32-bits, 64-bits floats |
i64 , i32 , i16 , i8 |
signed 16- ... integers |
u64 , u32 , u16 , u8 |
unsigned 16-bits, ... integers |
isize |
pointer-sized signed integers |
usize |
pointer-sized unsigned integers |
See: Rust Types
Formatting
// Single Placeholder
println!("{}", 1);
// Multiple Placeholder
println!("{} {}", 1, 3);
// Positional Arguments
println!("{0} is {1} {2}, also {0} is a {3} programming language", "Rust", "cool", "language", "safe");
// Named Arguments
println!("{country} is a diverse nation with unity.", country = "India");
// Placeholder traits :b for binary, :0x is for hex and :o is octal
println!("Let us print 76 is binary which is {:b} , and hex equivalent is {:0x} and octal equivalent is {:o}", 76, 76, 76);
// Debug Trait
println!("Print whatever we want to here using debug trait {:?}", (76, 'A', 90));
// New Format Strings in 1.58
let x = "world";
println!("Hello {x}!");
Printing Styles
// Prints the output
print!("Hello World\n");
// Appends a new line after printing
println!("Appending a new line");
// Prints as an error
eprint!("This is an error\n");
// Prints as an error with new line
eprintln!("This is an error with new line");
Variables
// Initializing and declaring a variable
let some_variable = "This_is_a_variable";
// Making a variable mutable
let mut mutable_variable = "Mutable";
// Assigning multiple variables
let (name, age) = ("ElementalX", 20);
// (Global) constant
const SCREAMING_SNAKE_CASE:i64 = 9;
Comments
// Line Comments
/*.............Block Comments */
/// Outer doc comments
//! Inner doc comments
See: Comment
Rust Types
Integer
let mut a: u32 = 8;
let b: u64 = 877;
let c: i64 = 8999;
let d = -90;
Floating-Point
let mut sixty_bit_float: f64 = 89.90;
let thirty_two_bit_float: f32 = 7.90;
let just_a_float = 69.69;
Boolean
let true_val: bool = true;
let false_val: bool = false;
let just_a_bool = true;
let is_true = 8 < 5; // => false
Character
let first_letter_of_alphabet = 'a';
let explicit_char: char = 'F';
let implicit_char = '8';
let emoji = "\u{1f600}"; // => 😀
String Literal
let community_name = "AXIAL";
let no_of_members: &str = "ten";
println!("The name of the community is {community_name} and it has {no_of_members} members");
See: Strings
Arrays
┌─────┬─────┬─────┬─────┬─────┬─────┐
| 92 | 97 | 98 | 99 | 98 | 94 |
└─────┴─────┴─────┴─────┴─────┴─────┘
0 1 2 3 4 5
let array: [i64; 6] = [92,97,98,99,98,94];
Multi-Dimensional Array
j0 j1 j2 j3 j4 j5
┌────┬────┬────┬────┬────┬────┐
i0 | 1 | 2 | 3 | 4 | 5 | 6 |
├────┼────┼────┼────┼────┼────┤
i1 | 6 | 5 | 4 | 3 | 2 | 1 |
└────┴────┴────┴────┴────┴────┘
let array: [[i64; 6] ;2] = [
[1,2,3,4,5,6],
[6,5,4,3,2,1]];
Mutable Array
let mut array: [i32 ; 3] = [2,6,10];
array[1] = 4;
array[2] = 6;
Use the mut
keyword to make it mutable.
Slices
let mut array: [ i64; 4] = [1,2,3,4];
let mut slices: &[i64] = &array[0..3] // Lower range is inclusive and upper range is exclusive
println!("The elements of the slices are : {slices:?}");
Vectors
let some_vector = vec![1,2,3,4,5];
A vector is declared using the vec!
macro.
Tuples
let tuple = (1, 'A' , "Cool", 78, true);
Rust Strings
String Literal
let cs:&str = "cheat sheet";
// => Share cheat sheet for developers
println!("Share {cs} for developers");
String Object
// Creating an empty string object
let my_string = String::new;
// Converting to a string object
let S_string = a_string.to_string()
// Creating an initialized string object
let lang = String::from("Rust");
println!("First language is {lang}");
.capacity()
let rand = String::from("Random String");
rand.capacity() // => 13
Calculates the capacity of the string in bytes.
.contains()
let name = String::from("ElementalX");
name.contains("Element") // => true
Checks if the substring is contained inside the original string or not.
Pushing a single character
let mut half_text = String::from("Hal");
half_text.push('f'); // => Half
Pushing an entire String
let mut hi = String::from("Hey there...");
hi.push_str("How are you doing??");
// => Hey there...How are you doing??
println!("{hi}");
Rust Operators
Comparison Operators
e == f |
e is equal to f |
e != f |
e is NOT equal to f |
e < f |
e is less than f |
e > f |
e is greater f |
e <= f |
e is less than or equal to f |
e >= f |
e is greater or equal to f |
let (e, f) = (1, 100);
let greater = f > e; // => true
let less = f < e; // => false
let greater_equal = f >= e; // => true
let less_equal = e <= f; // => true
let equal_to = e == f; // => false
let not_equal_to = e != f; // => true
Arithmetic Operators
a + b |
a is added to b |
a - b |
b is subtracted from a |
a / b |
a is divided by b |
a % b |
Gets remainder of a by dividing with b |
a * b |
a is multiplied with b |
let (a, b) = (4, 5);
let sum: i32 = a + b; // => 9
let subtractions: i32 = a - b; // => -1
let multiplication: i32 = a * b; // => 20
let division: i32 = a / b; // => 0
let modulus: i32 = a % b; // => 4
Bitwise Operators
Operator | Description |
---|---|
g & h |
Binary AND |
g | h |
Binary OR |
g ^ h |
Binary XOR |
!g |
Binary one's complement |
g << h |
Binary shift left |
g >> h |
Binary shift right |
let (g, h) = (0x1, 0x2);
let bitwise_and = g & h; // => 0
let bitwise_or = g | h; // => 3
let bitwise_xor = g ^ h; // => 3
let right_shift = g >> 2; // => 0
let left_shift = h << 4; // => 32
Logical Operators
Example | Meaning |
---|---|
c && d |
Both are true (AND) |
c || d |
Either is true (OR) |
!c |
c is false (NOT) |
let (c, d) = (true, false);
let and = c && d; // => false
let or = c || d; // => true
let not = !c; // => false
Compound Assignment Operator
let mut k = 9;
let mut l = k;
Operator | Description |
---|---|
k += l |
Add a value and assign, then k=9 |
k -= l |
Substrate a value and assign, then k=18 |
k /= l |
Divide a value and assign, then k=9 |
k *= l |
Multiply a value and assign, then k=81 |
k |= l |
Bitwise OR and assign, then k=89 |
Rust Flow Control
If Expression
let case1: i32 = 81;
let case2: i32 = 82;
if case1 < case2 {
println!("case1 is greater than case2");
}
If...Else Expression
let case3 = 8;
let case4 = 9;
if case3 >= case4 {
println!("case3 is better than case4");
} else {
println!("case4 is greater than case3");
}
If...Else...if...Else Expression
let foo = 12;
let bar = 13;
if foo == bar {
println!("foo is equal to bar");
} else if foo < bar {
println!("foo less than bar");
} else if foo != bar {
println!("foo is not equal to bar");
} else {
println!("Nothing");
}
If...Let Expression
let mut arr1:[i64 ; 3] = [1,2,3];
if let[1,2,_] = arr1{
println!("Works with array");
}
let mut arr2:[&str; 2] = ["one", "two"];
if let["Apple", _] = arr2{
println!("Works with str array too");
}
let tuple_1 = ("India", 7, 90, 90.432);
if let(_, 7, 9, 78.99) = tuple_1{
println!("Works with tuples too");
}
let tuple_2 = ( 9, 7, 89, 12, "Okay");
if let(9, 7,89, 12, blank) = tuple_2 {
println!("Everything {blank} mate?");
}
let tuple_3 = (89, 90, "Yes");
if let(9, 89, "Yes") = tuple_3{
println!("Pattern did match");
}
else {
println!("Pattern did not match");
}
Match Expression
let day_of_week = 2;
match day_of_week {
1 => {
println!("Its Monday my dudes");
},
2 => {
println!("It's Tuesday my dudes");
},
3 => {
println!("It's Wednesday my dudes");
},
4 => {
println!("It's Thursday my dudes");
},
5 => {
println!("It's Friday my dudes");
},
6 => {
println!("It's Saturday my dudes");
},
7 => {
println!("It's Sunday my dudes");
},
_ => {
println!("Default!")
}
};
Nested...If Expression
let nested_conditions = 89;
if nested_conditions == 89 {
let just_a_value = 98;
if just_a_value >= 97 {
println!("Greater than 97");
}
}
For Loop
for mut i in 0..15 {
i-=1;
println!("The value of i is : {i}");
}
While Loop
let mut check = 0;
while check < 11{
println!("Check is : {check}");
check+=1;
println!("After incrementing: {check}");
if check == 10{
break; // stop while
}
}
Loop keyword
loop {
println!("hello world forever!");
}
The infinite loop indicated.
Break Statement
let mut i = 1;
loop {
println!("i is {i}");
if i > 100 {
break;
}
i *= 2;
}
Continue Statement
for (v, c) in (0..10+1).enumerate(){
println!("The {c} number loop");
if v == 9{
println!("Here we go continue?");
continue;
}
println!{"The value of v is : {v}"};
}
Rust Functions
Basic function
fn print_message(){
println!("Hello, CheatSheets.zip!");
}
fn main(){
//Invoking a function in Rust.
print_message();
}
Pass by Value
fn main()
{
let x:u32 = 10;
let y:u32 = 20;
// => 200
println!("Calc: {}", cal_rect(x, y));
}
fn cal_rect(x:u32, y:u32) -> u32
{
x * y
}
Pass by Reference
fn main(){
let mut by_ref = 3; // => 3
power_of_three(&mut by_ref);
println!("{by_ref}"); // => 9
}
fn power_of_three(by_ref: &mut i32){
// de-referencing is important
*by_ref = *by_ref * *by_ref;
println!("{by_ref}"); // => 9
}
Returns
fn main(){
let (mut radius, mut pi) = (3.0, 3.14);
let(area, _perimeter) = calculate (
&mut radius,
&mut pi
);
println!("The area and the perimeter of the circle are: {area} & {_perimeter}");
}
fn calculate(radius : &mut f64, pi: &mut f64) -> (f64, f64){
let perimeter = 2.0 * *pi * *radius;
let area = *pi * *radius * *radius;
return (area, perimeter);
}
Arrays as Arguments
fn main(){
let mut array: [i32 ; 5] = [1,2,3,4,6];
print_arrays(array);
println!("The elements: {array:?}");
}
fn print_arrays(mut array:[i32; 5]) {
array[0] = 89;
array[1] = 90;
array[2] = 91;
array[3] = 92;
array[4] = 93;
println!("The elements: {array:?}");
}
Returning Arrays
fn main(){
let mut arr:[i32; 5] = [2,4,6,8,10];
multiply(arr);
println!("The array is : {:?}", multiply(arr));
}
fn multiply (mut arr: [i32 ; 5]) -> [i32 ; 5]{
arr[2] = 90;
for mut i in 0..5 {
arr[i] = arr[i] * arr[2];
}
return arr;
}
Error Handling
Basics
Rust has a robust error handling model that distinguishes between recoverable and unrecoverable errors. For recoverable errors, it uses Result<T, E>
, and for unrecoverable errors, it uses panic!
.
Recoverable Errors with Result
The Result
enum has two variants, Ok(T)
and Err(E)
, used for functions that can fail. Here's how to handle a recoverable error:
fn divide(numerator: f64, denominator: f64) -> Result<f64, &'static str> {
if denominator == 0.0 {
Err("Cannot divide by zero.")
} else {
Ok(numerator / denominator)
}
}
fn main() {
let result = divide(10.0, 0.0);
match result {
Ok(value) => println!("Result: {}", value),
Err(e) => println!("Error: {}", e),
}
}
Unrecoverable Errors with panic!
When the program encounters an unrecoverable error and cannot continue, use panic!
to terminate the program. It's commonly used for testing and dealing with bugs.
fn main() {
panic!("This function panicked and the program will terminate.");
}
Using panic!
immediately stops the program, providing an error message indicating what went wrong.
Propagating Errors
When you're not sure how to handle an error, you can return it to the calling function. This is known as propagating errors.
fn division_wrapper(numerator: f64, denominator: f64) -> Result<f64, &'static str> {
let result = divide(numerator, denominator)?;
Ok(result)
}
fn main() {
match division_wrapper(10.0, 0.0) {
Ok(value) => println!("Result: {}", value),
Err(e) => println!("Error: {}", e),
}
}
In this example, ?
is used to return the error if one occurs, simplifying error handling.
Ownership, Borrowing, and Lifetimes
Ownership
Ownership rules in Rust are based on three main principles:
- Each value in Rust has a variable called its owner.
- There can only be one owner at a time.
- When the owner goes out of scope, the value will be dropped.
fn main() {
let s = String::from("hello"); // s owns the string
takes_ownership(s); // ownership moved to the function
// println!("{s}"); // This would result in a compile-time error
}
fn takes_ownership(some_string: String) {
println!("{}", some_string);
}
Borrowing
Borrowing is Rust’s way of accessing data without taking ownership of it. This is done via references.
fn main() {
let s = String::from("hello");
let len = calculate_length(&s);
println!("The length of '{}' is {}.", s, len);
}
fn calculate_length(s: &String) -> usize { // s is a reference to a String
s.len()
}
Mutable References
You can have only one mutable reference to a particular piece of data in a particular scope. This restriction allows for mutation while avoiding data races.
fn main() {
let mut s = String::from("hello");
change(&mut s);
}
fn change(some_string: &mut String) {
some_string.push_str(", world");
}
Lifetimes
Lifetimes are a way of Rust to ensure all borrowed references are valid for the duration of that borrow. It prevents dangling references.
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}
fn main() {
let string1 = String::from("abcd");
let string2 = "xyz";
let result = longest(string1.as_str(), string2);
println!("The longest string is {}", result);
}
In this section, 'a
indicates that the return type has the same lifetime as the smallest of the input lifetimes.
Structs
Defining and Instantiating a Struct
// Define a struct
struct User {
username: String,
email: String,
sign_in_count: u64,
active: bool,
}
// Instantiate a struct
fn main() {
let user1 = User {
email: String::from("[email protected]"),
username: String::from("someusername123"),
active: true,
sign_in_count: 1,
};
}
Mutable Structs
To make an instance of a struct mutable, you use mut
keyword. All fields become mutable.
fn main() {
let mut user1 = User {
email: String::from("[email protected]"),
username: String::from("someusername123"),
active: true,
sign_in_count: 1,
};
user1.email = String::from("[email protected]");
}
Tuple Structs
Tuple structs have the added meaning the struct name provides but don’t have names associated with their fields; just the types.
struct Color(i32, i32, i32);
struct Point(i32, i32, i32);
fn main() {
let black = Color(0, 0, 0);
let origin = Point(0, 0, 0);
}
The impl
Block
You can define methods on structs (and enums) using impl
(implementation) blocks.
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}
}
fn main() {
let rect1 = Rectangle { width: 30, height: 50 };
println!("The area of the rectangle is {} square pixels.", rect1.area());
}
In Rust, methods can take ownership of self
, borrow self
immutably as we’ve done here, or borrow self
mutably.
Misc
Type Casting
let a_int = 90; // int
// int to float
let mut type_cast = (a_int as f64);
let orginal: char = 'I';
// char to int => 73
let type_casted: i64 = orginal as i64;
To perform type-casting in Rust one must use the as
keyword.
Borrowing
let mut foo = 4;
let mut borrowed_foo = &foo;
println!("{borrowed_foo}");
let mut bar = 3;
let mut mutable_borrowed_bar = &mut bar;
println!("{mutable_borrowed_bar}");
Here borrowed value borrows the value from value one using &
operator.
De-referencing
let mut borrow = 10;
let deref = &mut borrow;
println!("{}", *deref);
De-referencing in rust can be done using the *
operator
Variable Scope
{
// The scope limited to this braces
let a_number = 1;
}
println!("{a_number}");
This will produce error as the scope of the variable a_number
ends at the braces
Best Practices
Writing Idiomatic Rust
- Prefer
let
bindings, pattern matching, and if let expressions over using complex conditional logic. - Use enums to represent concepts that can be one of multiple different things.
- Leverage the iterator for more concise and expressive code that is also efficient.
- Adopt error handling conventions with
Result
andOption
types for clarity and safety.
Code Clarity
- Keep functions small and focused; each function should do one thing well.
- Prefer explicit over implicit: Explicit types and return values make code easier to understand.
- Utilize comments and documentation to explain "why" rather than "what". Rust’s support for documentation comments (
///
and//!
) integrates withcargo doc
for generating project documentation.
Memory Safety
- Follow ownership rules strictly to avoid memory leaks and dangling pointers.
- Use borrowing and lifetimes to ensure references are valid and to avoid unnecessary data copying.
Concurrency
- Take advantage of Rust's ownership and type system for writing safe concurrent code without data races.
- Prefer using the standard library's threading abstractions and async-await for handling concurrency.
Dependency Management
- Keep
Cargo.toml
organized and periodically review your dependencies to keep them up to date and secure. - Use cargo’s features to manage optional dependencies and feature flags for better compile-time configuration.
Testing and Linting
- Write unit tests for your functions to ensure they behave as expected.
- Use
rustfmt
to format your code andclippy
for linting. Consistent code style makes your code easier to read and maintain.
Performance
- Measure performance using benchmarks to understand the impact of changes.
- Prefer efficient data structures and algorithms that make the best use of system resources.
Learning and Community Involvement
- Stay updated with the latest Rust features and idioms by following the Rust Blog and engaging with the community through forums like the users forum and Reddit.
- Contribute to open-source Rust projects to gain experience and help the ecosystem grow.