Variables in Rust

Variables is one of the most crucial concepts in programming, they are used to store data in memory, so that later they can be accessed, modified, and used in different parts of the program.

In Rust, variables are immutable by default, which means that once you've assigned a value to a variable, you can't change it later.

This is a way that Rust pushes you to write code that's more predictable and easier to reason about, so that when a variable is sent to another thread, it is guaranteed that the value of the variable won't change, therefore the other threads can safely assume the variable is not going to change. This feature contributes to the safety and concurrency of Rust, that's not prevalent in other low-level programming languages.

However, we are used to changing values of variables after they've been declared, otherwise, writing code would be very hard and inconvenient. Hence, Rust provides you a way to make variables mutable, which means that you can change the value of a variable after it's been declared, but you have to explicitly declare the variable as mutable by using the mut keyword.

But when it comes to the choice between using mutable or immutable variables, it's preferred to use immutable variables whenever possible for performance optimizations. But this is not a strict rule, you can use mutable variables when you need to change the value of a variable after it's been declared whenever it's more convenient.

Let's have some examples to illustrate the concepts we've just discussed.

Create a new project using Cargo:

cargo new variables-in-rust

Learn Rust by Practice

Master Rust through hands-on coding exercises and real-world examples.

Declaring local variables

You can declare a local variable in Rust using the let keyword. Here's an example:

fn main() {
    let x = 5;
}

In the code above, the value of x is set to 5 and it's immutable by default, which means you can't change the value of x.

Let's try to change the value of the variable x and see what the compiler is going to say:

fn main() {
    let x = 5;
 
    x = 10; // Should cause a compile error
}

Can't change immutable variable Rust compile error Can't change immutable variable Rust compile error

As you can see, the Rust compiler gives a really nice error message that tells us exactly what's wrong with our code and gives us a hint on how to fix it.

cannot assign twice to immutable variable x, this error message tells us that we're trying to reassign a value to an immutable variable, which is not allowed in Rust.

In order to fix it, we can either remove the reassignment of the variable x or we can make the variable x mutable by using the mut keyword.

Let's fix the code by removing the reassignment of the variable x:

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

The code should compile and run without any errors. Let's run the code and see the output:

Immutable variables in Rust Immutable variables in Rust

You can not change the value of an immutable variable after it's been declared, without explicitly telling the compiler that the variable is mutable by using the mut keyword.

Mutable variables

Even though immutability has its own benefits and encouraged in Rust, there are cases in which using mutable variables is more convenient and easier to write. Rust let's you turn a variable into a mutable one by using the mut keyword.

Let's make the variable x mutable and try again:

fn main() {
    let mut x = 5; // x is now mutable
 
    println!("The value of x is: {}", x);
 
    x = 10; // Doesn't cause a compile error because x is mutable
 
    println!("The value of x is: {}", x)
}

Let's run the code and see the output:

Mutable variable Rust output Mutable variable Rust output

You can change the mutability of a variable by using the mut keyword. Once a variable is declared as mutable, you can change its value as many times as you want.

Shadowing

Unlike most programming languages, Rust allows you to declare multiple variables with the same name, but once a new variable is declared with the same name, the old variable is shadowed (it goes out of scope) and the new variable is used instead.

Let's demonstrate this with an example:

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

In the example above, we've declared a variable x with the value 5, then we've declared a new variable x with the value x + 1, in this case the first value of x is used for the x + 1 operation and then assigned to a different variable x, in that case the old variable x is shadowed and the new variable x is used instead which is the result of the x + 1 operation.

Same thing happens with the next line, in this case the value of x is 6 and it's multiplied by 2 and assigned to a new variable x, in this case the old variable x is shadowed and the new variable x is used instead. So the final value that should be printed is the last variable of x which results from the x * 2 operation.

Let's run the code and see the output:

Mutable variables in Rust Mutable variables in Rust

Constants

The let keyword let's us declare local variables that belong to a specific scope, but what if we want to have a global variable that can be accessed from any part of the program?

You can't declare a variable outside a function using the let keyword. In this example below, you'll get a compile error if you try to declare a variable outside the main function:

let x = 10;
 
fn main() {
    println!("Value of x = {}", x);
}

Variable declared outside a function in Rust Variable declared outside a function in Rust

The compiler gives us a message that says: "Consider using const or static instead of let for global variables".

Rust gives us another way to declare global variables, which is by using the const keyword. Here's an example of how to declare a constant in Rust:

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

Constant variables are not just immutable by default, they're only immutable and you can't use the mut keyword with them, and the type of the variable will not be inferred by the compiler, so you must always annotate the type of the constant.

Once a value is declared using the const syntax, it will be inlined directly in the program's binary, this means it will be stored neither in the stack nor the heap during runtime. This is a very important concept to understand, we'll discuss how Rust manages memory and stores variables in more details in the upcoming chapters.

Don't worry if you don't quite understand the concepts like stack, heap and type annotations yet, we'll discuss them in more detail later.

In the example above when we declared the constant MAX_POINTS, we've used the type annotation u32 to tell the compiler that the constant is an unsigned 32-bit integer, we will discuss all the different data types in Rust in the next chapter.

In Rust we can use the underscore _ to separate the digits in a number to make it more human-readable, in this case it is the same as writing 100000 (one hundred thousand).

Let's write some more examples and print them to the console:

const MAX_POINTS: u32 = 100_000;
const PI: f32 = 3.14;
const AUTHOR: &str = "John Doe";
 
fn main() {
    println!("The value of MAX_POINTS is: {}", MAX_POINTS);
    println!("The value of PI is: {}", PI);
    println!("The author is: {}", AUTHOR);
}

Let's run the code and see the output:

Constants in rust Constants in rust

In the example above, we've declared three constants, MAX_POINTS, PI, and AUTHOR. The first constant is an unsigned 32-bit integer with the value 100_000, the second constant is a 32-bit floating-point number with the value 3.14, and the third constant is a string slice with the value "John Doe".

You can declare local variables (inside functions) with the let keyword. For global variables, you can use the const keyword instead.

Scope

In Rust, each variable has a specific scope in which it's valid and can be accessed. The scope of a variable is determined by the block in which it's declared. A block is a piece of code that's surrounded by curly braces {}.

Whenever a variables goes out of scope, it means that the variable has been dropped, meaning that the memory that was allocated for the variable is freed and the variable can't be accessed anymore.

Let's have an example to illustrate the concept of scope in Rust:

fn main() {
    let x = 5; // x is valid from this point
 
    {
        let y = 10; // y is valid from this point
 
        println!("The value of y is: {}", y);
    } // y goes out of scope here
 
    println!("The value of x is: {}", x);
}

Scope example in rust Scope example in rust

In the example above, we've declared two variables x and y. The variable x is declared in the outer block and it's valid from the point it's declared until the end of the function. The variable y is declared in the inner block and it's valid from the point it's declared until the end of the inner block.

When the inner block ends, the variable y goes out of scope and is dropped, and we can't access it anymore. The variable x is still in scope and we can access it from the outer block.

If we tried to access the variable y outside the inner block, we would get a compile error because the variable y is not in scope anymore.

fn main() {
    let x = 5; // x is valid from this point
 
    {
        let y = 10; // y is valid from this point
 
        println!("The value of y is: {}", y);
    } // y goes out of scope here
 
    println!("The value of x is: {}", x);
    println!("The value of y is: {}", y);
}

Variable out of scope in rust Variable out of scope in rust

The Rust compiler tells us that the variable y is declared in the same function but in a different scope, and it's not accessible from the outer scope, which is quite an accurate error message.

In the next lesson, we're going to learn about the different data types in Rust and how you can give types to your variables.