Rust Programming: Variables and Data - Data Types
Rust is a statically typed language, meaning that the type of each variable must be known at compile time. This allows for strong compile-time error checking, leading to more reliable code. While Rust often infers types, understanding the available data types is crucial.
Here's a breakdown of Rust's fundamental data types:
1. Scalar Types
Scalar types represent single values.
Integers: Represent whole numbers. Rust provides several integer types with varying sizes:
i8,i16,i32,i64,i128: Signed integers (can be positive or negative). The number indicates the number of bits used to represent the integer.i32is the default integer type.u8,u16,u32,u64,u128: Unsigned integers (can only be positive).isize,usize: Integer types whose size depends on the architecture of the target machine (32-bit or 64-bit). Useful for indexing.
let x: i32 = 10; // Signed 32-bit integer let y: u64 = 1000; // Unsigned 64-bit integer let z = 5; // Rust infers this as i32Floating-Point Numbers: Represent numbers with decimal points.
f32: Single-precision floating-point number.f64: Double-precision floating-point number. This is the default floating-point type.
let pi: f64 = 3.14159; let temperature = 25.5; // Rust infers this as f64Boolean: Represents truth values.
bool: Can be eithertrueorfalse.
let is_active: bool = true; let is_finished = false;Character: Represents a single Unicode scalar value.
char: Represents a Unicode character (4 bytes).
let initial: char = 'J'; let emoji = '😀';
2. Compound Types
Compound types group multiple values together.
Tuples: A fixed-size, ordered list of elements. Elements can be of different types.
let my_tuple: (i32, f64, char) = (10, 3.14, 'A'); let (x, y, z) = my_tuple; // Destructuring - assigning tuple elements to variables println!("x: {}, y: {}, z: {}", x, y, z); let another_tuple = (5, "hello", true); // Type inferenceArrays: A fixed-size collection of elements of the same type.
let my_array: [i32; 5] = [1, 2, 3, 4, 5]; // Array of 5 i32s let first_element = my_array[0]; // Accessing elements by index println!("First element: {}", first_element);Important: Array sizes are part of the type.
[i32; 5]is a different type than[i32; 10].
3. Other Important Types
String: Represents a growable, UTF-8 encoded string. There are two main string types:
String: An owned, mutable string. Stored on the heap.&str: A string slice. A reference to a string. Immutable.
let mut my_string: String = String::from("Hello"); my_string.push_str(", world!"); println!("{}", my_string); let string_slice: &str = "This is a string slice"; println!("{}", string_slice);References: Allow you to access data without taking ownership. They are denoted by
&.let x = 10; let y = &x; // y is a reference to x println!("x: {}, y: {}", x, y);Functions: Functions are also types. You can assign functions to variables.
fn add(x: i32, y: i32) -> i32 { x + y } let my_function = add; let result = my_function(5, 3); println!("Result: {}", result);
Type Inference
Rust's type inference system is powerful. Often, you don't need to explicitly specify the type of a variable. The compiler can deduce it from the context.
let number = 42; // Rust infers i32
let pi_approx = 3.14; // Rust infers f64
However, it's good practice to explicitly specify types when:
- The type is not obvious.
- You want to improve code readability.
- You need to avoid potential type errors.
Shadowing
You can re-declare a variable with the same name within the same scope. This is called shadowing. The new variable shadows the previous one. Shadowing allows you to change the type of a variable.
let x = 5;
let x = x + 1; // Shadowing - x is now 6
let x = "hello"; // Shadowing - x is now a string
println!("{}", x);
Summary
Understanding Rust's data types is fundamental to writing correct and efficient code. The static typing system, combined with type inference, provides a balance between safety and convenience. Remember to choose the appropriate data type for your needs to optimize performance and prevent errors.