Variables & Constants
In this Rust tutorial we learn about basic temporary data containers in Rust called variables and constants. We cover variable and constant initialization, data type declaration, mutability and a technique known as shadowing.
What is a variable
A variable is one type of container that holds the application’s temporary data in the system’s memory (RAM) when it runs.
As an example, let’s consider a simple calculator. When we add two numbers together, the calculator stores the result in a temporary container. That way, we can use the result to perform additional arithmetic.
2 + 5 = x
x * 8 = y
The letters x and y in the example above, stores the resulting data from the arithmetic.
Variables like these are used everywhere within an application, and allow us to store data that’s only needed while the program runs.
When the application quits, the variables are cleaned and the space reserved for them in memory is released back to the system.
How to create (initialize) a variable
To create a variable, we write a simple expression that declares the variable and the initial value it holds to the compiler. This is known as initialization syntax that initializes the variable in memory.
Programming languages usually try to structure their expressions to look like simple english sentences, and Rust is no different.
We declare a variable by using the keyword let , followed by a name for the variable. Then, we use the equals symbol ( = ), followed by the value we want the variable to hold. Finally, we write a semicolon ( ; ) to indicate that this is where the statement is terminated.
let variable_name = variable_value;
fn main() {
// variable initialization
let name = "John Doe";
let age = "20";
println!("Hello, my name is {}\nI am {} years old\n",name, age);
}
In the example above, we initialize two variables. A variable called name, that holds a string value, and a variable called age, that holds an integer value.
The type of value stored in a variable determines how much space will be reserved for it in memory. A variable containing an integer, for example, will reserve 8 bytes in memory, depending on the system.
In languages like C, we have to specify the type when we declare or initialize a variable. In Rust however, the compiler will automatically infer the data type from the value we assign to the variable.
#include <stdio.h>
int main()
{
int number = 10;
return 0;
}
In the C example above, we tell the compiler explicitly that the variable number will hold an integer value.
In Rust, the compiler will look at the number and see that it’s an integer. It will then automatically use int as the data type for the variable.
fn main() {
let number = 10;
}
This is especially usefull when we don’t know which type of data the variable will contain.
It may be that an arithmetic calculation produces a result with a whole number in one instance, but in another it will produce a number with a floating point.
How to initialize a variable with an explicit type
Even though we can let the compiler automatically infer the data type, we may sometimes want to declare the type explicitly. Rust allows us to do so with a simple change in the syntax.
To declare a type explicitly, we add a colon after the variable name, followed by the data type we want.
let variable_name:data_type = variable_value;
fn main() {
let pi:f64 = 3.14;
println!("The value of PI is: {}\n", pi);
}
In the example above, we initialize a variable called pi to be a 64 bit floating point number with the word f64.
We’ll cover data types in a moment, but for now it’s enough to know that it is possible to explicitly declare a type for a variable in Rust.
How to change variable values
By default, variables in Rust are immutable. This means that once a value is assigned to a variable, it cannot be changed.
However, if we want to enable a variable to be mutable, we can specify it in its initialization by writing the keyword mut between the name and the keyword let .
let mut variable_name = variable_value;
Essentially, we are telling the Rust compiler that we want the variable to have both a read and write permission, instead of just a read persmission.
fn main() {
// mutable
let mut message = "Hello World";
println!("message: {}", message);
message = "Hello there";
println!("message: {}", message);
}
In the example above, we allow the message variable to be mutable by using the mut keyword. Further on in the application logic, we assign a new value to the variable and print it to the console.
Once a variable is initialized as mutable, we change the value by simply assigning a new value to the variable.
variable_name = new_value;
Notice that we didn’t specify the keywords let and mut again. This is only done when a variable is declared, not when it’s assigned new values.
fn main() {
// mutable
let mut result = 1;
println!("fibo: {}", result);
result = 1 + 2;
println!("fibo: {}", result);
result = 2 + 3;
println!("fibo: {}", result);
}
In the example above, we continually change the mutable result variable through the application logic.
Variable and constant Shadowing
Instead of declaring variables as mutable, risking possible race conditions, we can overwrite a variable with a different value.
Think of it as a changed copy of the original. This technique is called shadowing and is typically used to reuse a variable name, allowing us to work concurrently.
To do this, we reinitialize the variable with a different value, including the let keyword.
fn main() {
let message = "Hello World";
println!("{}",message);
// reinit
let message = "Hello there";
println!("{}",message);
}
It should be noted that shadowing does not replace mutability. It’s simply a safer and more cost efficient method in most cases.
What is a constant
A constant is a data container, similar to a variable. The difference between a constant and a variable is that the value of a constant cannot change at runtime.
We use constants when we know that a value should not be changed when the application runs.
We may want to set a hard limit to the amount of users able to use a service at once, or keep the gravity constant in a game.
How to initialize a constant
Unlike a variable, we do not use the let or mut keywords when initializing a constant. We use the const keyword instead.
Additionally, we must specify the constant’s data type.
const CONSTANT_NAME:data_type = constant_value;
It’s convention to write a constant name in all-caps, separating words with an underscore.
It allows us to easily see which data containers are constants when skimming the source code of a document.
fn main() {
const PI:f64 = 3.14;
const GRAVITY:f64 = 9.81;
println!("PI: {}", PI);
println!("G: {}", GRAVITY);
}
In the example above, we initialize two floating point constants and print them to the console.
Let’s see what happens when we try to change the value of a constant at runtime.
fn main() {
const GRAVITY:f64 = 9.81;
GRAVITY = 2.5;
println!("G: {}",GRAVITY);
}
The IDE should give us a warning before we even compile the code. Something like an ‘invalid left-hand side expression’ or ‘invalid lvalue expression’.
When we go ahead and run the program, the following error is raised.
error[E0070]: invalid left-hand side expression
--> src\main.rs:5:2
|
5 | GRAVITY = 2.5;
| ^^^^^^^^^^^^^ left-hand of expression not valid
error: aborting due to previous error
It shows that the left-hand side expression is not valid. This simply means that whatever is on the right side of the assignment operator, cannot be assigned to whatever is on the left side of the assignment operator.
Variables vs. Constants
Let’s explore the differences between constants and variables.
- A variable is initialized with the let keyword, whereas a constant is initialized with the const keyword.
- The data type declaration in a variable is optional, in a constant it is required.
- Constants are always immutable, variables have the option of being declared as mutable.
- A constant’s value cannot be set from any value that is computed at runtime, such as a function.
- A constant can be declared in any scope, even globally.
Summary: Points to remember
- Variables and constants are data containers that hold the temporary values of our program during runtime.
- Variables are immutable by default, but can be declared as mutable.
- Constants are immutable and cannot be declared as mutable.
- Both variables and constants must assign a value to them when they are declared.
- A variable does not have to declare its data type, the compiler will automatically infer it from the value.
- Shadowing is where we overwrite a variable by initializing it completely anew, including the let keyword.