A Rapid Guide to All Rust Features

This article is my recent learning journal on Rust. I find when it takes you 60min+ to watch a similar video on YouTube, it might need your 3–5 min to get the knowledge by reading the article.
Rust is a systems programming language that is designed to be safe, concurrent, and practical. Here are some of its notable features:
Memory Safety Without Garbage Collection:
Rust provides memory safety without needing a garbage collector, making it a suitable language for systems programming.
let v = vec![1, 2, 3]; // v owns its data
let v2 = v; // ownership of the data is moved to v2
// println!("{:?}", v); // this would not compile because v no longer owns the dataZero-Cost Abstractions:
Rust strives to provide the power of low-level control with high-level convenience and safety. Most of the abstractions in Rust do not incur any runtime overhead.
// Rust-style loop using an iterator
let sum: u32 = (0..5).sum();
// Equivalent C-style loop
let mut sum = 0;
for i in 0..5 {
sum += i;
}In both cases, the Rust compiler can optimize the code to the same or similar machine code, making the high-level, more readable version just as efficient as the low-level one. This is what is meant by “zero-cost abstractions” in Rust: you can use high-level abstractions without incurring a runtime performance cost.
Concurrency Without Data Races:
Rust’s type system and ownership guarantees make it much easier to write concurrent code that is free of data races.
use std::thread;
let handle = thread::spawn(|| {
// This code will be run in a new thread
println!("Hello from a new thread!");
});
// This code will be run in the main thread
println!("Hello from the main thread!");
handle.join().unwrap(); // Wait for the thread to finishIn concurrent programming, unwrap() is often used after join() to handle any potential errors that might have occurred in the spawned thread. If the thread panicked, join() will return an Err containing the argument given to panic!, and calling unwrap() will propagate the panic to the current thread.
Ownership and Borrowing:
Rust’s key feature is the ownership system. Each value in Rust has a variable that’s its owner. Borrowing allows access to data without taking ownership.
let mut x = 5; // declares a mutable variable x and initializes it with the value 5
{
let y = &mut x; // y borrows x: the &mut x syntax creates a mutable reference to x, and this reference is stored in the variable y.
*y += 1;
} // The {} block creates a new scope, and y goes out of scope at the end of this block, which ends the borrow.
println!("{}", x); // prints "6"The key point here is that while y is borrowing x as mutable, x cannot be accessed directly. This prevents data races because it ensures that only one thread can mutate x at a time. Once y goes out of scope and the borrow ends, x can be accessed directly again. This is all enforced at compile time by Rust's ownership system.
Package Manager:
Rust’s package manager, Cargo, is incredibly easy to use. Here’s how you specify a dependency in your Cargo.toml:
[dependencies] // where you list the crates that your project depends on
serde = "1.0" // serde is a framework for serializing and deserializing Rust data structures efficiently and generically. When you build your project with cargo build, Cargo will automatically download the serde crate and any other dependencies, compile them, and link them to your project.
Cargo also ensures that each dependency is compiled only once (per version), even if multiple crates depend on it, which can significantly speed up the build process. It also ensures that the exact versions of the dependencies that your project was tested with are used in the final build, which makes builds more reliable and reproducible.
Interoperability with C:
Rust provides a foreign function interface (FFI) to communicate with C libraries and can be called from C as well, making it easier to integrate with existing infrastructure.
// it declares that the functions inside it are implemented in C
extern "C" {
fn puts(s: *const u8); // written in Rust syntax, but it corresponds to the C function puts which takes a const char* as an argument.
}
fn main() {
unsafe { // The unsafe block is required because calling C functions from Rust is considered unsafe: the Rust compiler can't guarantee that the C code will uphold Rust's safety guarantees.
puts(b"Hello, world!\0".as_ptr());
}
}Powerful Type System:
Rust’s rich type system enforces at compile time properties such as: object lifetimes, mutability, struct layout, and more. This is done with zero-cost abstractions.
enum Option<T> {
Some(T),
None,
}The Option<T> enum is a powerful part of Rust's type system. It's used to represent a value that might be there, or it might not. This is a way of expressing nullability, but unlike many languages that use null, Option<T> makes the possibility of absence explicit and forces the programmer to handle it through the type system.
By using Option<T>, you signal to the compiler that a value could be None (or "null" in other languages). This forces you to handle the None case before you can use the value, which helps prevent null dereference bugs. This is a zero-cost abstraction because it doesn't add any runtime overhead.
Here is how you can use it:
fn find_name_by_id(id: u32) -> Option<String> {
// In a real application, this might look up the ID in a database
if id == 1 {
Some("Alice".to_string())
} else {
None
}
}
let name = find_name_by_id(1);
match name {
Some(n) => println!("Name: {}", n),
None => println!("No name found"),
}find_name_by_id returns an Option<String>. When we get the result, we have to handle both the Some case (where we found a name) and the None case (where we didn't). This ensures that we don't try to use a non-existent name.
Fearless Concurrency:
Rust makes it easy to write concurrent code by providing a number of key language features such as channels and other concurrency primitives.
// this code prints the vector [1, 2, 3] from a new thread, and then waits for that thread to finish
use std::thread; // imports Rust's built-in thread module, which provides functionality for spawning new threads.
let v = vec![1, 2, 3]; // a vector v with the elements 1, 2, and 3.
let handle = thread::spawn(move || { // This line starts a new thread. The move keyword before the closure means that ownership of any variables captured in the closure (in this case, v) will be moved into the new thread.
println!("{:?}", v); // prints the vector v. Because v was moved into the closure, it is now owned by the new thread, and this is where it is used.
});
handle.join().unwrap(); // This line waits for the new thread to finish. If the thread panicked, join() will return an Err and unwrap() will propagate the panic to the main thread. If the thread finished successfully, join() will return an Ok and unwrap() will return the result.





