![]() |
VOOZH | about |
We’re so glad you’re here. You can expect all the best TNS content to arrive Monday through Friday to keep you on top of the news and at the top of your game.
Check your inbox for a confirmation email where you can adjust your preferences and even join additional groups.
Follow TNS on your favorite social media networks.
Become a TNS follower on LinkedIn.
Check out the latest featured and trending stories while you wait for your first TNS newsletter.
rustup, the official installer that handles versions and toolchains with:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
rustup-init.exe, and then follow the command-line prompts in the installer (default settings are fine for most users).
Once installed, you can use the cargo CLI to create and manage projects. Use these commands to create and run a new project:
cargo new hello_rust cd hello_rust cargo run
rust-analyzer extension.
Option and Result.With this capability, Rust helps you avoid nulls. Instead, it uses Option<T> and Result<T, E> to explicitly handle missing or failed data.
Take a look at the code below.
In the main function, two division operations are performed using the divide function. The first divides 10.0 by 2.0, and the result is matched: if it returns Some(value), it prints the result; if it returns None, it prints an error message indicating division by zero. The second attempt divides 10.0 by 0.0, which triggers the None case and outputs the error message. This demonstrates how the divide function safely performs division between two f64 numbers by returning an Option<f64>, which helps prevent runtime errors like division by zero. If the divisor b is 0.0, the function returns None to indicate an invalid operation; otherwise, it returns Some(a / b) with the result.
This approach uses Rust’s Option type to handle potential failure in a controlled and type-safe manner, encouraging developers to explicitly deal with the possibility of an absent result.
Cargo.toml file:
This configuration adds the following crates as dependencies:
serde → A framework for serializing and deserializing data formats such as JSON, YAML, TOML, etc. The derive feature allows automatic generation of boilerplate code via macros.tokio → An asynchronous runtime used to write concurrent programs using async/await syntax, commonly used in networked applications.clap → A powerful and ergonomic crate for parsing command-line arguments, making it easy to build user-friendly CLI tools.cargo build or cargo run, and Cargo will automatically download, compile and link these crates to your project, as shown in the screenshot below.
for loops, you can use iterators to chain operations such as map, filter and fold directly on collections. This results in code that is often more concise, expressive and efficient.
let nums = vec![1, 2, 3, 4, 5]; let doubled: Vec<_> = nums.iter().map(|x| x * 2).collect();
nums is a vector containing the integers 1 through 5.nums.iter() returns an iterator over references to each element (&i32)..map(|x| x * 2) applies a function that multiplies each item by 2. Since x is a reference, Rust automatically dereferences it during the operation..collect() gathers the transformed items into a new Vec, which is assigned to doubled.describe takes an i32 value and returns a string slice describing the value based on pattern matching. It uses a match expression to compare value against multiple patterns:
0, it returns Zero.1..=10, it returns Between 1 and 10._), it returns Greater than 10.&'static str indicates the function returns a string with a static lifetime, such as string literals.
#[cfg(test)], which tells the compiler to include the module only when running tests. Inside the tests module, use super::*; brings the items from the parent module (like the divide function) into scope. It contains two unit tests:
it_divides_correctly checks that dividing 10.0 by 2.0 returns Some(5.0).it_handles_divide_by_zero verifies that dividing by zero returns None.#[test] attribute marks each function as a test case.
Using this approach allows you to automatically verify that code behaves as expected, making it easier to catch bugs and ensure code correctness over time.
Run the test with the command:
cargo test
cargo fmt # Format your code cargo clippy # Catch potential issues
clap.
use clap::Parser;
/// Simple calculator
#[derive(Parser)]
struct Args {
#[arg(short, long)]
a: i32,
#[arg(short, long)]
b: i32,
}
fn main() {
let args = Args::parse();
println!("{} + {} = {}", args.a, args.b, args.a + args.b);
}
clap crate to create a simple command-line calculator that adds two integers. The Args struct is annotated with #[derive(Parser)], which automatically generates code to parse command-line arguments. Each field (a and b) is marked with #[arg(short, long)], allowing the user to specify them using short flags like -a or long flags like --a. In the main function, Args::parse() reads and parses the arguments from the command line, then the program prints the sum of a and b.
let v = vec![1, 2, 3];
let moved = v;
// println!("{:?}", v); // ❌ Error: value used after move
v containing the values [1, 2, 3] and then assigns the ownership to moved. In Rust, most types like Vec<T> do not implement the Copy trait, so assigning them to another variable transfers ownership rather than duplicating the data.
As a result, trying to access v after the move (such as with println!("{:?}", v)) causes a compile-time error: value used after move. This behavior enforces Rust’s ownership rules, which ensure memory safety without needing a garbage collector.
To sort this mess, you can clone or reference when needed.
let v = vec![1, 2, 3];
let cloned = v.clone();
println!("{:?}", v); // ✅ OK
v with the values [1, 2, 3], then creates a deep copy of it using v.clone(), storing the copy in cloned. Unlike a move, cloning explicitly duplicates the data in memory, so both v and cloned own separate copies of the same values. This allows v to remain valid and accessible after the cloning operation, so calling println!("{:?}", v) works without error.
Cloning is useful when you need to preserve the original data while also passing or storing a copy elsewhere, though it can be more costly in terms of performance compared to moving.
fn print_length(v: Vec<i32>) {
println!("{}", v.len());
}
let v = vec![1, 2, 3];
// print_length(v.clone()); // ❌ Unnecessary clone
print_length that takes ownership of a Vec<i32> and prints its length. When calling print_length(v.clone()), it creates a full copy of the vector just to pass it to the function, which is often unnecessary and inefficient.
Instead, since print_length only needs to read the length and not modify or keep the vector, it would be better to change its parameter to take a reference (&Vec<i32>) so that the original vector can be borrowed without cloning.
Cloning here is an unnecessary performance cost because Rust allows safe, non-owning references that avoid duplicating data when only read access is needed.
To fix this, use borrowing.
fn print_length(v: &Vec<i32>) {
println!("{}", v.len());
}
print_length(&v); // ✅ OK
print_length that takes an immutable reference to a Vec<i32> instead of taking ownership. When calling print_length(&v), the vector v is borrowed, not moved, allowing the function to read its length without taking ownership. This means v remains usable after the function call, and no data is cloned or copied, making it both efficient and safe.
Using references like this is idiomatic in Rust when you only need to read from a value, as it avoids unnecessary memory allocation and preserves ownership.
fn get_str<'a>() -> &'a str {
let s = String::from("hello");
&s // ❌ Error: returns reference to local variable
}
s, which is dropped when the function ends, so the reference is invalid.
There are two options to fix this. The first is to return an owned string.
fn get_str() -> String {
String::from("hello")
}
fn get_str<'a>(input: &'a str) -> &'a str {
input
}
let x = 5;
let x = x + 1;
println!("{}", x); // prints 6
&String Instead of &strfn print_str(s: &str) {
println!("{}", s);
}
let s = String::from("hello");
print_str(&s); // works fine
// But...
fn print_string(s: &String) {
println!("{}", s);
}
print_string(&s); // valid but less flexible
&str is more flexible than &String. Use &str for function parameters unless you need ownership or specific String methods.
for Loop Without mut When Neededlet mut v = vec![1, 2, 3];
for x in v {
x += 1; // ❌ Error: cannot assign to `x` because it is not mutable
}
x is immutable by default.
To fix this, use the mut keyword to make x a mutable reference.
let mut v = vec![1, 2, 3];
for x in &mut v {
*x += 1; // works because x is a mutable reference
}
fn main() {
let v = vec![3, 1, 2];
v.sort(); // ❌ Error: cannot borrow immutable local variable `v` as mutable
}
.sort() requires the vector to be mutable and in scope.
let mut v = vec![3, 2, 1]; v.sort(); // works fine on mutable vector
mut When Modifying Variableslet x = 5; x = 6; // ❌ Error: cannot assign twice to immutable variable
mut keyword lets you modify variables.
let mut x = 5; x = 6; // works fine
Resultuse std::fs::File;
fn main() {
let f = File::open("hello.txt"); // ❌ warning: unused Result
}
File::open returns a Result. Ignoring it can hide errors and will cause compiler warnings.
Fix the above issue with:
use std::fs::File;
fn main() {
let f = match File::open("hello.txt") {
Ok(file) => file,
Err(e) => panic!("Error opening file: {}", e),
};
}
hello.txt using File::open, which returns a Result type indicating success (Ok) or failure (Err). It uses a match expression to handle both cases: If the file is successfully opened, the file handle is stored in the variable f; if an error occurs (e.g., the file doesn’t exist), the program immediately calls panic!, which crashes the program and prints the error message.
This approach is useful for basic error handling, especially when file access is critical and the program cannot proceed without it. However, in production code, more graceful error handling, like logging or fallback behavior, is often preferred over panicking.
.expect() Without a Helpful Messagelet v: Vec<i32> = Vec::new();
println!("{}", v[0]); // panics at runtime
v.get(0).expect("Oops"); // panics with generic message
.expect() without descriptive messages makes debugging harder.
v.get(0).expect("Index 0 should exist in vector");
.iter() When .into_iter() or .iter_mut() Is Neededlet mut v = vec![1, 2, 3];
for x in v.iter() {
*x += 1; // ❌ error: cannot assign to `*x`
}
.iter() gives immutable references. Use .iter_mut() to solve this error.
for x in v.iter_mut() {
*x += 1;
}
clippy and rust-analyzer. These tools can help you catch subtle mistakes and improve your code style.