Variables and Mutability

As mentioned in the “Storing Values with Variables” section, by default variables are immutable. This is one of many nudges Rust gives you to write your code in a way that takes advantage of the safety and easy concurrency that Rust offers. However, you still have the option to make your variables mutable. Let’s explore how and why Rust encourages you to favor immutability and why sometimes you might want to opt out.

When a variable is immutable, once a value is bound to a name, you can’t change that value. To illustrate this, let’s generate a new project called variables in your projects directory by using cargo new variables.

Then, in your new variables directory, open src/main.rs and replace its code with the following code. This code won’t compile just yet, we’ll first examine the immutability error.

Filename: src/main.rs

  1. fn main() {
  2. let x = 5;
  3. println!("The value of x is: {x}");
  4. x = 6;
  5. println!("The value of x is: {x}");
  6. }

Save and run the program using cargo run. You should receive an error message, as shown in this output:

  1. $ cargo run
  2. Compiling variables v0.1.0 (file:///projects/variables)
  3. error[E0384]: cannot assign twice to immutable variable `x`
  4. --> src/main.rs:4:5
  5. |
  6. 2 | let x = 5;
  7. | -
  8. | |
  9. | first assignment to `x`
  10. | help: consider making this binding mutable: `mut x`
  11. 3 | println!("The value of x is: {x}");
  12. 4 | x = 6;
  13. | ^^^^^ cannot assign twice to immutable variable
  14. For more information about this error, try `rustc --explain E0384`.
  15. error: could not compile `variables` due to previous error

This example shows how the compiler helps you find errors in your programs. Compiler errors can be frustrating, but really they only mean your program isn’t safely doing what you want it to do yet; they do not mean that you’re not a good programmer! Experienced Rustaceans still get compiler errors.

The error message indicates that the cause of the error is that you cannot assign twice to immutable variable x, because you tried to assign a second value to the immutable x variable.

It’s important that we get compile-time errors when we attempt to change a value that’s designated as immutable because this very situation can lead to bugs. If one part of our code operates on the assumption that a value will never change and another part of our code changes that value, it’s possible that the first part of the code won’t do what it was designed to do. The cause of this kind of bug can be difficult to track down after the fact, especially when the second piece of code changes the value only sometimes. The Rust compiler guarantees that when you state a value won’t change, it really won’t change, so you don’t have to keep track of it yourself. Your code is thus easier to reason through.

But mutability can be very useful, and can make code more convenient to write. Although variables are immutable by default, you can make them mutable by adding mut in front of the variable name as you did in Chapter 2. Adding mut also conveys intent to future readers of the code by indicating that other parts of the code will be changing this variable’s value.

For example, let’s change src/main.rs to the following:

Filename: src/main.rs

  1. fn main() {
  2. let mut x = 5;
  3. println!("The value of x is: {x}");
  4. x = 6;
  5. println!("The value of x is: {x}");
  6. }

When we run the program now, we get this:

  1. $ cargo run
  2. Compiling variables v0.1.0 (file:///projects/variables)
  3. Finished dev [unoptimized + debuginfo] target(s) in 0.30s
  4. Running `target/debug/variables`
  5. The value of x is: 5
  6. The value of x is: 6

We’re allowed to change the value bound to x from 5 to 6 when mut is used. Ultimately, deciding whether to use mutability or not is up to you and depends on what you think is clearest in that particular situation.

Constants

Like immutable variables, constants are values that are bound to a name and are not allowed to change, but there are a few differences between constants and variables.

First, you aren’t allowed to use mut with constants. Constants aren’t just immutable by default—they’re always immutable. You declare constants using the const keyword instead of the let keyword, and the type of the value must be annotated. We’re about to cover types and type annotations in the next section, “Data Types,” so don’t worry about the details right now. Just know that you must always annotate the type.

Constants can be declared in any scope, including the global scope, which makes them useful for values that many parts of code need to know about.

The last difference is that constants may be set only to a constant expression, not the result of a value that could only be computed at runtime.

Here’s an example of a constant declaration:

  1. #![allow(unused)]
  2. fn main() {
  3. const THREE_HOURS_IN_SECONDS: u32 = 60 * 60 * 3;
  4. }

The constant’s name is THREE_HOURS_IN_SECONDS and its value is set to the result of multiplying 60 (the number of seconds in a minute) by 60 (the number of minutes in an hour) by 3 (the number of hours we want to count in this program). Rust’s naming convention for constants is to use all uppercase with underscores between words. The compiler is able to evaluate a limited set of operations at compile time, which lets us choose to write out this value in a way that’s easier to understand and verify, rather than setting this constant to the value 10,800. See the Rust Reference’s section on constant evaluation for more information on what operations can be used when declaring constants.

Constants are valid for the entire time a program runs, within the scope they were declared in. This property makes constants useful for values in your application domain that multiple parts of the program might need to know about, such as the maximum number of points any player of a game is allowed to earn or the speed of light.

Naming hardcoded values used throughout your program as constants is useful in conveying the meaning of that value to future maintainers of the code. It also helps to have only one place in your code you would need to change if the hardcoded value needed to be updated in the future.

Shadowing

As you saw in the guessing game tutorial in Chapter 2, you can declare a new variable with the same name as a previous variable. Rustaceans say that the first variable is shadowed by the second, which means that the second variable is what the compiler will see when you use the name of the variable. In effect, the second variable overshadows the first, taking any uses of the variable name to itself until either it itself is shadowed or the scope ends. We can shadow a variable by using the same variable’s name and repeating the use of the let keyword as follows:

Filename: src/main.rs

  1. fn main() {
  2. let x = 5;
  3. let x = x + 1;
  4. {
  5. let x = x * 2;
  6. println!("The value of x in the inner scope is: {x}");
  7. }
  8. println!("The value of x is: {x}");
  9. }

This program first binds x to a value of 5. Then it creates a new variable x by repeating let x =, taking the original value and adding 1 so the value of x is then 6. Then, within an inner scope created with the curly brackets, the third let statement also shadows x and creates a new variable, multiplying the previous value by 2 to give x a value of 12. When that scope is over, the inner shadowing ends and x returns to being 6. When we run this program, it will output the following:

  1. $ cargo run
  2. Compiling variables v0.1.0 (file:///projects/variables)
  3. Finished dev [unoptimized + debuginfo] target(s) in 0.31s
  4. Running `target/debug/variables`
  5. The value of x in the inner scope is: 12
  6. The value of x is: 6

Shadowing is different from marking a variable as mut, because we’ll get a compile-time error if we accidentally try to reassign to this variable without using the let keyword. By using let, we can perform a few transformations on a value but have the variable be immutable after those transformations have been completed.

The other difference between mut and shadowing is that because we’re effectively creating a new variable when we use the let keyword again, we can change the type of the value but reuse the same name. For example, say our program asks a user to show how many spaces they want between some text by inputting space characters, and then we want to store that input as a number:

  1. fn main() {
  2. let spaces = " ";
  3. let spaces = spaces.len();
  4. }

The first spaces variable is a string type and the second spaces variable is a number type. Shadowing thus spares us from having to come up with different names, such as spaces_str and spaces_num; instead, we can reuse the simpler spaces name. However, if we try to use mut for this, as shown here, we’ll get a compile-time error:

  1. fn main() {
  2. let mut spaces = " ";
  3. spaces = spaces.len();
  4. }

The error says we’re not allowed to mutate a variable’s type:

  1. $ cargo run
  2. Compiling variables v0.1.0 (file:///projects/variables)
  3. error[E0308]: mismatched types
  4. --> src/main.rs:3:14
  5. |
  6. 2 | let mut spaces = " ";
  7. | ----- expected due to this value
  8. 3 | spaces = spaces.len();
  9. | ^^^^^^^^^^^^ expected `&str`, found `usize`
  10. For more information about this error, try `rustc --explain E0308`.
  11. error: could not compile `variables` due to previous error

Now that we’ve explored how variables work, let’s look at more data types they can have.