Rust Fundamentals: Core Language Concepts
Introduction
Every programming language shares common concepts like variables, basic types, functions, comments, and control flow. Understanding these fundamentals in Rust will accelerate your learning journey and provide a solid foundation for more advanced concepts.
Variables and Mutability
Rust is a strongly-typed language with type inference capabilities. This feature shouldn't be confused with dynamic typing - Rust's type system is strict but smart enough to figure out types when they're obvious.
Variable Declaration
By default, variables in Rust are immutable. Here's how to declare variables:
let a = 123; // Immutable variable
let mut b = 10; // Mutable variable
Understanding Immutability
Rust's immutability by default is a design choice that promotes safer concurrent code. Consider this example:
let a = 123;
// The following operations are not allowed:
// a = "abc"; // Error: Type mismatch
// a = 4.56; // Error: Precision loss
// a = 456; // Error: Cannot modify immutable variable
Variable Shadowing
Rust allows variable shadowing, which is different from mutation:
let x = 5;
let x = x + 1; // Creates a new variable, shadows the previous 'x'
let x = x * 2; // Creates another new variable
println!("Value: {}", x); // Outputs: Value: 12
Type Annotations
While Rust can infer types, explicit type annotations are sometimes useful:
let a: u64 = 123; // Explicitly declare as unsigned 64-bit integer
let b = 123; // Inferred as i32 (signed 32-bit integer)
Data Types
Scalar Types
-
Integers
let signed: i32 = -42; // 32-bit signed let unsigned: u32 = 42; // 32-bit unsigned let big_number: i64 = 1000; // 64-bit signed
-
Floating-Point
let float: f64 = 3.14; // 64-bit float let precise: f32 = 3.14159; // 32-bit float
-
Boolean
let is_active: bool = true; let is_ready = false; // Type inference
-
Character
let letter: char = 'A'; // Single quotes for char let emoji = '😀'; // Supports Unicode
Control Flow
If Expressions
let number = 7;
if number < 5 {
println!("Less than 5");
} else if number > 10 {
println!("Greater than 10");
} else {
println!("Between 5 and 10");
}
Loops
-
loop - Infinite Loop
let mut counter = 0; loop { counter += 1; if counter == 10 { break; } }
-
while Loop
let mut number = 3; while number != 0 { println!("{}!", number); number -= 1; }
-
for Loop
for number in 1..4 { println!("{}!", number); }
Ownership System
Rust's ownership system is its most unique feature, managing memory safety without garbage collection.
Key Concepts
-
Ownership Rules
- Each value has exactly one owner
- Only one owner at a time
- Value is dropped when owner goes out of scope
-
Example of Ownership Transfer
let s1 = String::from("hello"); let s2 = s1; // s1's ownership moves to s2 // println!("{}", s1); // Error: s1 is no longer valid
-
Borrowing with References
fn main() { let s = String::from("hello"); let len = calculate_length(&s); // Borrow s println!("Length of '{}' is {}.", s, len); } fn calculate_length(s: &String) -> usize { s.len() }
Error Handling
Rust uses two main types for error handling:
Result<T, E>
fn divide(a: i32, b: i32) -> Result<i32, String> {
if b == 0 {
Err(String::from("Division by zero"))
} else {
Ok(a / b)
}
}
Option<T>
fn get_element(index: usize, vec: &Vec<i32>) -> Option<i32> {
if index < vec.len() {
Some(vec[index])
} else {
None
}
}
Best Practices
-
Use Immutability by Default
- Only make variables mutable when necessary
- Helps prevent bugs and makes code easier to reason about
-
Type Annotations
- Use type annotations when type inference isn't obvious
- Makes code more readable and self-documenting
-
Error Handling
- Prefer Result over panic!
- Use Option for values that might not exist
-
Variable Naming
- Use descriptive names
- Follow Rust's snake_case convention
Common Pitfalls to Avoid
-
Ownership Confusion
// Wrong let s1 = String::from("hello"); let s2 = s1; println!("{}", s1); // Error // Correct let s1 = String::from("hello"); let s2 = s1.clone(); // Explicitly clone if you need both println!("{}", s1); // OK
-
Mutability vs. Shadowing
// Mutability let mut x = 5; x = 6; // Changes value, same variable // Shadowing let x = 5; let x = x + 1; // Creates new variable
Next Steps
After mastering these fundamentals, you'll be ready to explore:
- Advanced ownership concepts
- Structs and enums
- Pattern matching
- Modules and packages
- Concurrency
Remember: Rust's strict rules might feel constraining at first, but they help prevent common programming errors and make your code more reliable.