After completing this exercise, you will be able to:
rustc and cargoprintln!, print!, and std::ioif, else, match)loop, while, and forRust is a systems programming language developed by Mozilla Research, with its first stable release in 2015. It was designed to be a safe, concurrent, and practical language that gives programmers control over low-level details while preventing common programming errors at compile time.
Rust consistently ranks as one of the most loved programming languages in developer surveys due to its unique combination of safety and performance. Key features include:
match expressions for handling different casesRust source files use the .rs extension. We will use both the Rust compiler (rustc) for simple programs and Cargo for project management.
Create a file called hello.rs with the following content:
fn main() {
println!("Hello, World!");
}
Open a terminal and navigate to the directory containing your file. Compile and run with:
rustc hello.rs
./hello
You should see Hello, World! printed to the console.
For larger projects, use Cargo, Rust's build system and package manager:
# Create a new project
cargo new hello_cargo
cd hello_cargo
# Build and run
cargo run
Cargo creates a project structure with src/main.rs containing a hello world program.
rustc for quick single-file experiments. Use cargo for any real project—it handles dependencies, building, testing, and more.
| Code Element | Explanation |
|---|---|
fn main() |
Declares the main function—the entry point of every Rust program |
{ } |
Curly braces define the function body (block) |
println!(...) |
A macro that prints text followed by a newline (note the !) |
; |
Semicolon ends the statement |
Main, main, and MAIN are all different identifiers. By convention, function names use snake_case (lowercase with underscores).
When you run cargo new project_name, Cargo creates:
project_name/
├── Cargo.toml # Project configuration and dependencies
└── src/
└── main.rs # Your main source file
The Cargo.toml file contains project metadata:
[package]
name = "hello_cargo"
version = "0.1.0"
edition = "2021"
[dependencies]
Rust is a statically typed language with type inference. You must declare variables with let, but the compiler can often infer the type automatically.
| Type | Size | Range |
|---|---|---|
i8 |
8 bits | -128 to 127 |
i16 |
16 bits | -32,768 to 32,767 |
i32 |
32 bits | -2,147,483,648 to 2,147,483,647 |
i64 |
64 bits | -9.2Ă—1018 to 9.2Ă—1018 |
i128 |
128 bits | Very large range |
isize |
Platform dependent | 32 or 64 bits based on architecture |
Unsigned types are also available: u8, u16, u32, u64, u128, usize.
i32. When you write let x = 42; without a type annotation, Rust infers i32.
| Type | Size | Description |
|---|---|---|
f32 |
32 bits | Single precision (~7 significant digits) |
f64 |
64 bits | Double precision (~15 significant digits) |
f64. When you write let x = 3.14;, Rust infers f64.
| Type | Description | Example |
|---|---|---|
bool |
Boolean (true or false) | let flag: bool = true; |
char |
Unicode character (4 bytes) | let letter: char = 'A'; |
String |
Growable, owned string | let name = String::from("Alice"); |
&str |
String slice (borrowed) | let greeting: &str = "Hello"; |
fn main() {
// Type annotation (explicit)
let age: i32 = 25;
let gpa: f64 = 3.85;
let name: &str = "Alice";
let enrolled: bool = true;
let grade: char = 'A';
// Type inference (compiler figures out the type)
let count = 100; // inferred as i32
let price = 19.99; // inferred as f64
let message = "Hello"; // inferred as &str
// Output the values
println!("Name: {}", name);
println!("Age: {}", age);
println!("GPA: {:.2}", gpa);
println!("Enrolled: {}", enrolled);
println!("Grade: {}", grade);
}
By default, variables in Rust are immutable. To make a variable mutable, use mut:
fn main() {
let x = 5; // immutable
// x = 6; // ERROR: cannot assign twice to immutable variable
let mut y = 5; // mutable
println!("y = {}", y);
y = 6; // OK: y is mutable
println!("y = {}", y);
}
mut when you actually need to change a variable's value.
Use const for values that never change and must be known at compile time:
const MAX_POINTS: u32 = 100_000;
const PI: f64 = 3.14159265359;
fn main() {
println!("Max points: {}", MAX_POINTS);
println!("Pi: {}", PI);
}
SCREAMING_SNAKE_CASE by convention. You can use underscores in numeric literals for readability: 100_000 is the same as 100000.
Rust allows you to declare a new variable with the same name, which "shadows" the previous one:
fn main() {
let x = 5;
println!("x = {}", x);
let x = x + 1; // shadows previous x
println!("x = {}", x);
let x = x * 2; // shadows again
println!("x = {}", x);
// Can even change type when shadowing
let spaces = " "; // &str
let spaces = spaces.len(); // usize (shadows with different type)
println!("spaces = {}", spaces);
}
Rust provides several macros for output. The ! indicates these are macros, not regular functions.
| Macro | Description |
|---|---|
println!(...) |
Prints text followed by a newline |
print!(...) |
Prints text without a newline |
eprintln!(...) |
Prints to stderr with newline (for errors) |
eprint!(...) |
Prints to stderr without newline |
fn main() {
// Basic output
println!("Hello, World!");
// Output without newline
print!("This ");
print!("is ");
print!("on one line");
println!(); // Just prints a newline
// Output to stderr (for error messages)
eprintln!("This is an error message");
}
Use {} as placeholders for values:
fn main() {
let name = "Alice";
let age = 25;
let gpa = 3.85;
// Basic placeholders
println!("Name: {}", name);
println!("Age: {}", age);
println!("GPA: {}", gpa);
// Multiple values
println!("{} is {} years old", name, age);
// Positional arguments
println!("{0} is {1}. {0} studies hard.", name, age);
// Named arguments
println!("{student} has a {score} GPA", student = name, score = gpa);
}
fn main() {
let pi = 3.14159265359;
let big_number = 1234567;
let percentage = 0.856;
// Decimal places
println!("Pi: {:.2}", pi); // 2 decimal places
println!("Pi: {:.4}", pi); // 4 decimal places
// Width and alignment
println!("[{:10}]", 42); // right-aligned, width 10
println!("[{:<10}]", 42); // left-aligned, width 10
println!("[{:^10}]", 42); // center-aligned, width 10
println!("[{:0>10}]", 42); // zero-padded, width 10
// Combined width and precision
println!("Value: {:8.2}", pi); // width 8, 2 decimal places
// Different bases
println!("Decimal: {}", 255);
println!("Binary: {:b}", 255);
println!("Octal: {:o}", 255);
println!("Hex (lower): {:x}", 255);
println!("Hex (upper): {:X}", 255);
}
fn main() {
let name = "Alice";
// Width for strings
println!("[{:15}]", name); // right-padded to 15 chars
println!("[{:>15}]", name); // right-aligned
println!("[{:^15}]", name); // centered
// Truncation
println!("[{:.3}]", "Hello"); // truncate to 3 chars
}
{:?} (debug format) or {:#?} (pretty debug format) to print complex data structures.
fn main() {
let numbers = [1, 2, 3, 4, 5];
println!("Debug: {:?}", numbers);
println!("Pretty debug: {:#?}", numbers);
}
Reading input in Rust requires the std::io module. Input is always read as a String and must be parsed to other types.
use std::io;
fn main() {
println!("What is your name?");
let mut input = String::new();
io::stdin()
.read_line(&mut input)
.expect("Failed to read line");
// trim() removes the trailing newline
let name = input.trim();
println!("Hello, {}!", name);
}
To read numbers, parse the string input:
use std::io;
fn main() {
println!("Enter your age:");
let mut input = String::new();
io::stdin()
.read_line(&mut input)
.expect("Failed to read line");
let age: i32 = input.trim().parse().expect("Please enter a valid number");
println!("You are {} years old.", age);
println!("Next year you will be {}.", age + 1);
}
Use match to handle invalid input gracefully:
use std::io;
fn main() {
println!("Enter a number:");
let mut input = String::new();
io::stdin()
.read_line(&mut input)
.expect("Failed to read line");
let number: i32 = match input.trim().parse() {
Ok(n) => n,
Err(_) => {
println!("That's not a valid number!");
return;
}
};
println!("You entered: {}", number);
}
Since input reading is verbose, create a reusable function:
use std::io::{self, Write};
fn read_line(prompt: &str) -> String {
print!("{}", prompt);
io::stdout().flush().unwrap(); // Ensure prompt is displayed
let mut input = String::new();
io::stdin()
.read_line(&mut input)
.expect("Failed to read line");
input.trim().to_string()
}
fn read_i32(prompt: &str) -> i32 {
loop {
let input = read_line(prompt);
match input.parse() {
Ok(n) => return n,
Err(_) => println!("Invalid number. Please try again."),
}
}
}
fn read_f64(prompt: &str) -> f64 {
loop {
let input = read_line(prompt);
match input.parse() {
Ok(n) => return n,
Err(_) => println!("Invalid number. Please try again."),
}
}
}
fn main() {
let name = read_line("Enter your name: ");
let age = read_i32("Enter your age: ");
let gpa = read_f64("Enter your GPA: ");
println!();
println!("Name: {}", name);
println!("Age: {}", age);
println!("GPA: {:.2}", gpa);
}
io::stdout().flush().unwrap() call ensures that print! output appears immediately before waiting for input. Without it, the prompt might not display until after you press Enter.
In Rust, = is for assignment and == is for equality comparison:
fn main() {
let mut x = 10; // initial assignment
println!("x = {}", x);
x = 20; // reassignment (requires mut)
println!("x = {}", x);
x = x + 5; // compound expression
println!("x = {}", x);
}
| Operator | Operation | Example | Result |
|---|---|---|---|
+ |
Addition | 5 + 3 |
8 |
- |
Subtraction | 5 - 3 |
2 |
* |
Multiplication | 5 * 3 |
15 |
/ |
Division | 7 / 3 |
2 (integer division) |
% |
Modulus (remainder) | 7 % 3 |
1 |
7.0 / 3.0 gives 2.333...
fn main() {
let mut x = 10;
x += 5; // x = x + 5
println!("After += 5: {}", x);
x -= 3; // x = x - 3
println!("After -= 3: {}", x);
x *= 2; // x = x * 2
println!("After *= 2: {}", x);
x /= 4; // x = x / 4
println!("After /= 4: {}", x);
x %= 3; // x = x % 3
println!("After %= 3: {}", x);
}
++ or -- operators. Use x += 1 or x -= 1 instead.
| Operator | Meaning | Example |
|---|---|---|
== |
Equal to | x == 5 |
!= |
Not equal to | x != 5 |
< |
Less than | x < 10 |
> |
Greater than | x > 10 |
<= |
Less than or equal | x <= 10 |
>= |
Greater than or equal | x >= 10 |
| Operator | Meaning | Example |
|---|---|---|
&& |
Logical AND | x > 0 && x < 100 |
|| |
Logical OR | x < 0 || x > 100 |
! |
Logical NOT | !flag |
fn main() {
let age = 25;
let has_license = true;
// Comparison operators
println!("age == 25: {}", age == 25);
println!("age != 30: {}", age != 30);
println!("age > 18: {}", age > 18);
// Logical operators
println!("Can drive: {}", age >= 16 && has_license);
println!("Is teenager: {}", age >= 13 && age <= 19);
println!("NOT has_license: {}", !has_license);
}
Use as to convert between numeric types:
fn main() {
let x: i32 = 10;
let y: f64 = 3.0;
// Convert i32 to f64 for floating-point division
let result = x as f64 / y;
println!("10 / 3.0 = {:.4}", result);
// Convert f64 to i32 (truncates)
let pi = 3.14159;
let truncated = pi as i32;
println!("{} as i32 = {}", pi, truncated);
// Convert between integer sizes
let small: i8 = 42;
let big: i64 = small as i64;
println!("i8 {} as i64 = {}", small, big);
}
Rust provides several constructs for controlling program flow based on conditions.
The basic if statement tests a condition and executes code accordingly:
fn main() {
let number = 7;
if number > 5 {
println!("{} is greater than 5", number);
}
}
fn main() {
let number = 3;
if number > 5 {
println!("{} is greater than 5", number);
} else {
println!("{} is not greater than 5", number);
}
}
fn main() {
let score = 85;
if score >= 90 {
println!("Grade: A");
} else if score >= 80 {
println!("Grade: B");
} else if score >= 70 {
println!("Grade: C");
} else if score >= 60 {
println!("Grade: D");
} else {
println!("Grade: F");
}
}
In Rust, if is an expression that returns a value:
fn main() {
let condition = true;
// if can return a value
let number = if condition { 5 } else { 6 };
println!("The number is: {}", number);
// More complex example
let score = 85;
let grade = if score >= 90 {
'A'
} else if score >= 80 {
'B'
} else if score >= 70 {
'C'
} else if score >= 60 {
'D'
} else {
'F'
};
println!("Grade: {}", grade);
}
if as an expression, both branches must return the same type. Notice there's no semicolon after the value in each branch—this makes it a return value rather than a statement.
The match expression is a powerful control flow construct for pattern matching:
fn main() {
let number = 3;
match number {
1 => println!("One"),
2 => println!("Two"),
3 => println!("Three"),
4 => println!("Four"),
5 => println!("Five"),
_ => println!("Something else"), // _ is the catch-all pattern
}
}
match expression must be exhaustive—it must cover every possible value. The _ pattern matches anything not explicitly listed.
fn main() {
let score = 85;
let grade = match score {
90..=100 => 'A', // Range from 90 to 100 (inclusive)
80..=89 => 'B',
70..=79 => 'C',
60..=69 => 'D',
0..=59 => 'F',
_ => '?', // Invalid score
};
println!("Score: {}, Grade: {}", score, grade);
// Multiple patterns with |
let day = 6;
let day_type = match day {
1 | 2 | 3 | 4 | 5 => "Weekday",
6 | 7 => "Weekend",
_ => "Invalid day",
};
println!("Day {} is a {}", day, day_type);
}
fn main() {
let command = "quit";
match command {
"start" => println!("Starting..."),
"stop" => println!("Stopping..."),
"pause" => println!("Pausing..."),
"quit" | "exit" => println!("Goodbye!"),
_ => println!("Unknown command: {}", command),
}
}
You can add conditions to match arms with if guards:
fn main() {
let number = 4;
match number {
n if n < 0 => println!("{} is negative", n),
n if n == 0 => println!("zero"),
n if n % 2 == 0 => println!("{} is positive and even", n),
n => println!("{} is positive and odd", n),
}
}
fn main() {
let age = 25;
let has_id = true;
if age >= 18 {
if has_id {
println!("Entry permitted.");
} else {
println!("ID required for entry.");
}
} else {
println!("Must be 18 or older.");
}
}
Rust provides three types of loops: loop, while, and for.
The loop keyword creates an infinite loop. Use break to exit:
fn main() {
let mut counter = 0;
loop {
counter += 1;
println!("Counter: {}", counter);
if counter >= 5 {
break;
}
}
println!("Loop finished!");
}
The loop expression can return a value with break:
fn main() {
let mut counter = 0;
let result = loop {
counter += 1;
if counter == 10 {
break counter * 2; // Return counter * 2
}
};
println!("The result is: {}", result);
}
The while loop continues while a condition is true:
fn main() {
let mut number = 5;
while number > 0 {
println!("{}!", number);
number -= 1;
}
println!("Liftoff!");
}
The for loop iterates over collections or ranges:
fn main() {
// Iterating over a range (1 to 5, exclusive of 6)
println!("Counting 1 to 5:");
for i in 1..6 {
println!(" {}", i);
}
// Inclusive range (1 to 5, inclusive)
println!("\nUsing inclusive range:");
for i in 1..=5 {
println!(" {}", i);
}
}
a..b for a range from a to b-1 (exclusive end), or a..=b for a range from a to b (inclusive end).
fn main() {
let numbers = [10, 20, 30, 40, 50];
// Iterate over elements
println!("Elements:");
for num in numbers {
println!(" {}", num);
}
// Iterate with index using enumerate()
println!("\nWith indices:");
for (index, num) in numbers.iter().enumerate() {
println!(" numbers[{}] = {}", index, num);
}
}
fn main() {
println!("Countdown:");
for i in (1..=5).rev() {
println!(" {}", i);
}
println!(" Liftoff!");
}
fn main() {
// break - exit the loop early
println!("Using break:");
for i in 1..10 {
if i == 5 {
println!(" Breaking at {}", i);
break;
}
println!(" {}", i);
}
// continue - skip to next iteration
println!("\nUsing continue (skip even numbers):");
for i in 1..=10 {
if i % 2 == 0 {
continue; // Skip even numbers
}
println!(" {}", i);
}
}
Use labels to break or continue outer loops from nested loops:
fn main() {
'outer: for i in 1..=3 {
println!("Outer loop: i = {}", i);
for j in 1..=3 {
println!(" Inner loop: j = {}", j);
if i == 2 && j == 2 {
println!(" Breaking outer loop!");
break 'outer;
}
}
}
println!("Done!");
}
fn main() {
// Sum numbers 1 to 10
let mut sum = 0;
for i in 1..=10 {
sum += i;
}
println!("Sum of 1-10: {}", sum);
// Find factorial
let n = 5;
let mut factorial = 1;
for i in 1..=n {
factorial *= i;
}
println!("{}! = {}", n, factorial);
// Count iterations
let mut count = 0;
for _ in 0..100 { // _ ignores the loop variable
count += 1;
}
println!("Counted to: {}", count);
}
use std::io::{self, Write};
fn main() {
let number: i32;
loop {
print!("Enter a positive number: ");
io::stdout().flush().unwrap();
let mut input = String::new();
io::stdin().read_line(&mut input).expect("Failed to read");
match input.trim().parse() {
Ok(n) if n > 0 => {
number = n;
break;
}
Ok(_) => println!("Please enter a POSITIVE number."),
Err(_) => println!("Invalid input. Please enter a number."),
}
}
println!("You entered: {}", number);
}
Functions in Rust are declared with the fn keyword. They allow you to organize code into reusable blocks.
fn greet() {
println!("Hello, World!");
}
fn main() {
greet(); // Call the function
greet(); // Call it again
}
fn greet_person(name: &str) {
println!("Hello, {}!", name);
}
fn print_sum(a: i32, b: i32) {
println!("{} + {} = {}", a, b, a + b);
}
fn main() {
greet_person("Alice");
greet_person("Bob");
print_sum(5, 3);
print_sum(10, 20);
}
Use -> to specify the return type. The last expression without a semicolon is returned:
fn add(a: i32, b: i32) -> i32 {
a + b // No semicolon = return value
}
fn multiply(a: i32, b: i32) -> i32 {
return a * b; // Explicit return also works
}
fn is_even(n: i32) -> bool {
n % 2 == 0
}
fn main() {
let sum = add(5, 3);
println!("5 + 3 = {}", sum);
let product = multiply(4, 7);
println!("4 * 7 = {}", product);
println!("Is 10 even? {}", is_even(10));
println!("Is 7 even? {}", is_even(7));
}
a + b (expression, returns value) and a + b; (statement, returns nothing) is crucial. Forgetting to omit the semicolon is a common mistake.
fn min_max(a: i32, b: i32) -> (i32, i32) {
if a < b {
(a, b)
} else {
(b, a)
}
}
fn divide_with_remainder(dividend: i32, divisor: i32) -> (i32, i32) {
let quotient = dividend / divisor;
let remainder = dividend % divisor;
(quotient, remainder)
}
fn main() {
let (min, max) = min_max(10, 5);
println!("Min: {}, Max: {}", min, max);
let (quot, rem) = divide_with_remainder(17, 5);
println!("17 / 5 = {} remainder {}", quot, rem);
}
// Calculate factorial
fn factorial(n: u64) -> u64 {
if n <= 1 {
1
} else {
n * factorial(n - 1)
}
}
// Check if a number is prime
fn is_prime(n: u32) -> bool {
if n < 2 {
return false;
}
if n == 2 {
return true;
}
if n % 2 == 0 {
return false;
}
let mut i = 3;
while i * i <= n {
if n % i == 0 {
return false;
}
i += 2;
}
true
}
// Calculate average of three numbers
fn average(a: f64, b: f64, c: f64) -> f64 {
(a + b + c) / 3.0
}
fn main() {
println!("5! = {}", factorial(5));
println!("10! = {}", factorial(10));
println!("Is 17 prime? {}", is_prime(17));
println!("Is 18 prime? {}", is_prime(18));
println!("Average of 85, 90, 78: {:.2}", average(85.0, 90.0, 78.0));
}
Rust has two main sequential collection types: fixed-size arrays and growable vectors.
Arrays have a fixed size known at compile time:
fn main() {
// Array with explicit type annotation
let numbers: [i32; 5] = [1, 2, 3, 4, 5];
// Array with inferred type
let grades = [85, 90, 78, 92, 88];
// Array initialized with same value
let zeros = [0; 10]; // 10 elements, all 0
// Accessing elements
println!("First number: {}", numbers[0]);
println!("Third grade: {}", grades[2]);
// Array length
println!("Array length: {}", numbers.len());
// Print all elements
println!("All grades: {:?}", grades);
}
fn main() {
let numbers = [10, 20, 30, 40, 50];
// Iterate over elements
println!("Elements:");
for num in numbers {
println!(" {}", num);
}
// Iterate with index
println!("\nWith indices:");
for (i, num) in numbers.iter().enumerate() {
println!(" numbers[{}] = {}", i, num);
}
// Calculate sum
let mut sum = 0;
for num in numbers {
sum += num;
}
println!("\nSum: {}", sum);
}
Vectors can grow or shrink at runtime:
fn main() {
// Create an empty vector
let mut numbers: Vec<i32> = Vec::new();
// Add elements
numbers.push(10);
numbers.push(20);
numbers.push(30);
println!("After pushing: {:?}", numbers);
println!("Length: {}", numbers.len());
// Create with initial values using vec! macro
let mut grades = vec![85, 90, 78];
grades.push(92);
grades.push(88);
println!("Grades: {:?}", grades);
// Access elements
println!("First grade: {}", grades[0]);
// Safe access with get() - returns Option
match grades.get(10) {
Some(grade) => println!("Grade at 10: {}", grade),
None => println!("No grade at index 10"),
}
}
fn main() {
let mut nums = vec![3, 1, 4, 1, 5, 9, 2, 6];
println!("Original: {:?}", nums);
println!("Length: {}", nums.len());
println!("Is empty: {}", nums.is_empty());
// First and last elements
println!("First: {:?}", nums.first());
println!("Last: {:?}", nums.last());
// Remove last element
let popped = nums.pop();
println!("Popped: {:?}", popped);
println!("After pop: {:?}", nums);
// Insert at position
nums.insert(0, 100);
println!("After insert at 0: {:?}", nums);
// Remove at position
nums.remove(0);
println!("After remove at 0: {:?}", nums);
// Check if contains value
println!("Contains 4: {}", nums.contains(&4));
// Clear all elements
nums.clear();
println!("After clear: {:?}", nums);
println!("Is empty: {}", nums.is_empty());
}
fn main() {
let mut names: Vec<String> = Vec::new();
names.push(String::from("Alice"));
names.push(String::from("Bob"));
names.push(String::from("Charlie"));
println!("Names: {:?}", names);
// Iterate and print
for name in &names {
println!("Hello, {}!", name);
}
// Using vec! macro with strings
let fruits = vec!["apple", "banana", "cherry"];
for fruit in &fruits {
println!("I like {}.", fruit);
}
}
fn main() {
let scores = vec![85.0, 92.0, 78.0, 90.0, 88.0];
// Sum using iterator
let sum: f64 = scores.iter().sum();
println!("Sum: {}", sum);
// Average
let average = sum / scores.len() as f64;
println!("Average: {:.2}", average);
// Find min and max
let min = scores.iter().cloned().reduce(f64::min).unwrap();
let max = scores.iter().cloned().reduce(f64::max).unwrap();
println!("Min: {}, Max: {}", min, max);
// Or using min_by/max_by for floats
let min2 = scores.iter().cloned().fold(f64::INFINITY, f64::min);
let max2 = scores.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
println!("Min: {}, Max: {}", min2, max2);
}
This program demonstrates input, output, functions, control structures, and loops working together:
use std::io::{self, Write};
// Helper function to read a line of input
fn read_line(prompt: &str) -> String {
print!("{}", prompt);
io::stdout().flush().unwrap();
let mut input = String::new();
io::stdin()
.read_line(&mut input)
.expect("Failed to read line");
input.trim().to_string()
}
// Helper function to read an integer with validation
fn read_positive_int(prompt: &str, min_value: i32) -> i32 {
loop {
let input = read_line(prompt);
match input.parse::<i32>() {
Ok(n) if n >= min_value => return n,
Ok(_) => println!("Please enter a number >= {}.", min_value),
Err(_) => println!("Invalid input. Please enter a number."),
}
}
}
// Check if a number is prime
fn is_prime(n: i32) -> bool {
if n < 2 {
return false;
}
if n == 2 {
return true;
}
if n % 2 == 0 {
return false;
}
let mut i = 3;
while i * i <= n {
if n % i == 0 {
return false;
}
i += 2;
}
true
}
// Find the smallest factor of n (assumes n > 1 and n is not prime)
fn smallest_factor(n: i32) -> i32 {
if n % 2 == 0 {
return 2;
}
let mut i = 3;
while i * i <= n {
if n % i == 0 {
return i;
}
i += 2;
}
n // n is prime, return itself
}
// Count prime numbers from 2 to n
fn count_primes_up_to(n: i32) -> i32 {
let mut count = 0;
for i in 2..=n {
if is_prime(i) {
count += 1;
}
}
count
}
fn main() {
println!("=== Prime Number Analyzer ===");
println!();
loop {
// Get valid input
let n = read_positive_int("Enter a positive integer (>= 2): ", 2);
// Check if prime and display result
if is_prime(n) {
println!("{} is a prime number!", n);
} else {
let factor = smallest_factor(n);
println!("{} is not prime. Its smallest factor is {}.", n, factor);
}
// Count primes up to n
let prime_count = count_primes_up_to(n);
println!("There are {} prime numbers from 2 to {}.", prime_count, n);
// Ask to continue
println!();
let response = read_line("Check another number? (y/n): ");
if response.to_lowercase() != "y" {
break;
}
println!();
}
println!("Goodbye!");
}
Option 1: Using rustc
rustc prime_analyzer.rs
./prime_analyzer
Option 2: Using Cargo
cargo new prime_analyzer
cd prime_analyzer
# Replace src/main.rs with the code above
cargo runWrite a program that:
Write a program that:
rand crate)Write a program that:
Write a program that:
use std::io::{self, Write};
// TODO: Create a helper function to read input
fn read_line(prompt: &str) -> String {
print!("{}", prompt);
io::stdout().flush().unwrap();
let mut input = String::new();
io::stdin()
.read_line(&mut input)
.expect("Failed to read line");
input.trim().to_string()
}
// TODO: Create a function to convert Fahrenheit to Celsius
fn fahrenheit_to_celsius(f: f64) -> f64 {
// Formula: C = (F - 32) * 5/9
_____
}
fn main() {
println!("=== Temperature Converter ===");
println!();
loop {
// TODO: Read temperature from user
let input = read_line("Enter temperature in Fahrenheit: ");
// TODO: Parse the input to f64
let fahrenheit: f64 = match input.parse() {
Ok(n) => n,
Err(_) => {
println!("Invalid number. Please try again.");
continue;
}
};
// TODO: Convert and display result
let celsius = _____;
println!("{}°F = {:.2}°C", fahrenheit, celsius);
// TODO: Ask if user wants to continue
println!();
let response = read_line("Convert another? (y/n): ");
if response.to_lowercase() != "y" {
break;
}
println!();
}
println!("Goodbye!");
}
| Task | Rust Syntax |
|---|---|
| Declare immutable variable | let x = 5; |
| Declare mutable variable | let mut x = 5; |
| Declare with type | let x: i32 = 5; |
| Constant | const MAX: i32 = 100; |
| Print with newline | println!("Hello"); |
| Print without newline | print!("Hello"); |
| Print variable | println!("x = {}", x); |
| Print with formatting | println!("{:.2}", 3.14159); |
| Read line of input | io::stdin().read_line(&mut s) |
| Parse string to number | s.trim().parse::<i32>() |
| If statement | if x > 5 { } else { } |
| Match expression | match x { 1 => ..., _ => ... } |
| Infinite loop | loop { break; } |
| While loop | while x > 0 { } |
| For loop (range) | for i in 1..=10 { } |
| For loop (collection) | for item in &vec { } |
| Function definition | fn name(x: i32) -> i32 { } |
| Create empty vector | let mut v: Vec<i32> = Vec::new(); |
| Create vector with values | let v = vec![1, 2, 3]; |
| Add to vector | v.push(4); |
| Vector length | v.len() |
| Access vector element | v[0] or v.get(0) |
| Command | Description |
|---|---|
cargo new project_name |
Create a new Rust project |
cargo build |
Compile the project (debug mode) |
cargo build --release |
Compile with optimizations |
cargo run |
Build and run the project |
cargo run -- arg1 arg2 |
Run with command-line arguments |
cargo check |
Check for errors without building |
cargo clean |
Remove compiled artifacts |
cargo test |
Run unit tests |
cargo fmt |
Format code according to style guidelines |
cargo clippy |
Run linter for additional warnings |
rustc file.rs |
Compile single file without Cargo |
| Concept | Rust | Pascal |
|---|---|---|
| Program entry | fn main() { } |
begin ... end. |
| Variable declaration | let x: i32 = 5; |
var x: integer; |
| Constants | const X: i32 = 5; |
const X = 5; |
| Print with newline | println!("text"); |
writeln('text'); |
| Print without newline | print!("text"); |
write('text'); |
| Read input | io::stdin().read_line(&mut s) |
readln(s); |
| Assignment | x = 5; |
x := 5; |
| Equality test | x == 5 |
x = 5 |
| Not equal | x != 5 |
x <> 5 |
| Logical AND | && |
and |
| Logical OR | || |
or |
| Logical NOT | ! |
not |
| If statement | if cond { } else { } |
if cond then ... else ... |
| For loop | for i in 1..=10 { } |
for i := 1 to 10 do |
| While loop | while cond { } |
while cond do |
| Repeat/loop until | loop { if cond { break; } } |
repeat ... until cond; |
| Function | fn name() -> i32 { } |
function name: integer; |
| Procedure | fn name() { } |
procedure name; |
| Comments | // line or /* block */ |
{ block } or (* block *) |
| Integer division | a / b (for integers) |
a div b |
| Modulus | a % b |
a mod b |
| Case sensitivity | Case-sensitive | Case-insensitive |
| Rust Type | Size | Pascal Equivalent |
|---|---|---|
i8 |
8-bit signed | ShortInt |
i16 |
16-bit signed | SmallInt |
i32 |
32-bit signed | Integer / LongInt |
i64 |
64-bit signed | Int64 |
u8 |
8-bit unsigned | Byte |
u16 |
16-bit unsigned | Word |
u32 |
32-bit unsigned | Cardinal / LongWord |
u64 |
64-bit unsigned | QWord |
f32 |
32-bit float | Single |
f64 |
64-bit float | Double / Real |
bool |
Boolean | Boolean |
char |
Unicode (4 bytes) | Char (1 byte) |
String |
Growable string | String |
&str |
String slice | N/A |
Vec<T> |
Dynamic array | array of T |
[T; N] |
Fixed array | array[0..N-1] of T |
mut – Variables are immutable by default; use let mut to allow modification= instead of == – Use = for assignment, == for comparison{ } for if/else/loop bodies, even for single statements++ or -- – Rust doesn't have these operators; use x += 1 or x -= 1if/while conditionsResult – Operations that can fail return Result; handle with match, expect(), or ?read_line includes the newline; always use .trim() before parsingif expressions – Both branches must return the same type when using if as an expressioncargo check for fast error checking without full compilationcargo fmt to automatically format your codecargo clippy for additional linting and suggestions{:?} in println! to debug-print complex valuesdbg!(&variable) macro for quick debuggingmatch over long if-else if chains1..=10) in for loops instead of manual counters_ to ignore values you don't need (e.g., for _ in 0..10)expect() for error handling while learning, then move to proper Result handling| Specifier | Description | Example | Output |
|---|---|---|---|
{} |
Default display | println!("{}", 42); |
42 |
{:?} |
Debug format | println!("{:?}", vec![1,2]); |
[1, 2] |
{:#?} |
Pretty debug | println!("{:#?}", vec![1,2]); |
Multi-line |
{:.2} |
2 decimal places | println!("{:.2}", 3.14159); |
3.14 |
{:8} |
Width 8, right-aligned | println!("{:8}", 42); |
42 |
{:<8} |
Width 8, left-aligned | println!("{:<8}", 42); |
42 |
{:^8} |
Width 8, centered | println!("{:^8}", 42); |
42 |
{:08} |
Zero-padded width 8 | println!("{:08}", 42); |
00000042 |
{:b} |
Binary | println!("{:b}", 42); |
101010 |
{:x} |
Hexadecimal (lower) | println!("{:x}", 255); |
ff |
{:X} |
Hexadecimal (upper) | println!("{:X}", 255); |
FF |
{:o} |
Octal | println!("{:o}", 64); |
100 |
{:e} |
Scientific notation | println!("{:e}", 1234.5); |
1.2345e3 |
let x = 5; and let mut x = 5;?.trim() after reading input with read_line?1..5 and 1..=5?println! and print!?a + b different from a + b; at the end of a function?_ pattern match in a match expression?String and a &str?let x = 5; creates an immutable variable that cannot be changed. let mut x = 5; creates a mutable variable that can be reassigned.read_line includes the newline character (\n) that the user pressed. .trim() removes whitespace including this newline, which is necessary before parsing to a number.1..5 is an exclusive range (1, 2, 3, 4). 1..=5 is an inclusive range (1, 2, 3, 4, 5).println! adds a newline after the output. print! does not, so subsequent output appears on the same line.a + b is an expression that returns a value (the sum). With a semicolon, a + b; is a statement that evaluates and discards the result, returning () (unit/nothing)._ is the catch-all or wildcard pattern that matches anything not explicitly handled by other arms.'outer: for ... { for ... { break 'outer; } }String is an owned, growable, heap-allocated string. &str is a borrowed reference to string data (a "string slice") that can point to a String or a string literal.