Variables in Rust

Variable Declaration

In Rust, variables are immutable by default. You declare a variable using the let keyword:

fn main() {
    let x = 5;
    println!("The value of x is: {}", x);

    // This would cause an error
    // x = 6; // cannot assign twice to immutable variable
}

Mutability

To make a variable mutable, use the mut keyword:

fn main() {
    let mut x = 5;
    println!("The value of x is: {}", x);

    x = 6; // This is valid because x is mutable
    println!("The value of x is now: {}", x);
}

Constants

Constants are values that are bound to a name and cannot change. They're declared using the const keyword:

fn main() {
    const MAX_POINTS: u32 = 100_000;
    println!("The maximum points is: {}", MAX_POINTS);
}

Constants differ from immutable variables in that:

  • They must be annotated with a type
  • They can be declared in any scope, including the global scope
  • They can only be set to a constant expression, not the result of a function call or any other value computed at runtime

Shadowing

You can declare a new variable with the same name as a previous variable, effectively "shadowing" the previous variable:

fn main() {
    let x = 5;

    let x = x + 1; // This creates a new variable x that shadows the old one

    {
        let x = x * 2; // This shadows x within this scope
        println!("The value of x in the inner scope is: {}", x); // 12
    }

    println!("The value of x is: {}", x); // 6
}

Shadowing allows you to change a variable's type while reusing the same name.

Type Annotations

When you want to explicitly specify a variable's type, you can use a type annotation:

fn main() {
    // Here the guess variable is expected to be of u32 type
    let guess: u32 = "42".parse().expect("Not a number!");
}

Working with Types

While a detailed guide on data types is available in a separate document, it's important to understand how variables interact with types in Rust:

Type Inference

Rust can often infer what type you want to use based on the value and how you use it:

fn main() {
    let x = 5;        // Rust infers this as i32
    let y = 3.0;      // Rust infers this as f64
    let active = true; // Rust infers this as bool
}

Using Type Annotations for Clarity

Even when Rust can infer the type, you might want to add type annotations for clarity:

fn main() {
    let x: i32 = 5;
    let y: f64 = 3.0;
    let active: bool = true;
}

Type Annotations for Disambiguation

Sometimes Rust needs your help to know which specific type to use:

fn main() {
    // Without a type annotation, Rust doesn't know which numeric type to use
    let guess = "42".parse().expect("Not a number!");  // Error!

    // With a type annotation, Rust knows exactly what to do
    let guess: u32 = "42".parse().expect("Not a number!");  // Works!
}

For more information on the various data types available in Rust, please refer to the Data Types guide.