Module: Variables and Data

Data Types

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. i32 is 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 i32
    
  • Floating-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 f64
    
  • Boolean: Represents truth values.

    • bool: Can be either true or false.
    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 inference
    
  • Arrays: 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.