Rust Programming: Variables and Data Types
Welcome to the world of Rust programming! In this article, we will explore the fundamentals of variables and data types.
Variables
In Rust, we create variables using the let statement.
let a = 10; // immutable
let mut b = 15; // mutable
By default, variables are immutable, meaning their values can’t change. You can make them mutable by using mut.
let mut x = 3;
x = 8;
Constants
Constants are like immutable variables but with some differences. You aren’t allowed to use mut with constants. Constants aren’t just immutable by default — they’re always immutable. They are declared using const, and their type must be annotated. Rust’s naming convention for constants is to use all uppercase with underscores between words.
Here’s an example of a constant declaration:
const THREE_MINUTES_IN_SECONDS: u32 = 60 * 3;
Shadowing
Rust allows variable shadowing, declaring a new variable with the same name as a previous variable.
fn main() {
let age = 25;
let age = age + 1;
{
let age = age * 2;
println!("In the inner scope, age is: {age}");
}
println!("Outside the inner scope, age is: {age}");
}
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.
Data Types
Every value in Rust is of a certain data type, which tells Rust what kind of data is being specified so it knows how to work with that data. Rust has scalar and compound data types.
Scalar Data Type
Scalar types represent single values. Rust has four primary scalar types: integers, floating-point numbers, booleans, and characters.
- Integer:
An integer is a whole number that can be either positive or negative. It can be signed (with a positive or negative sign) or unsigned (only positive) and has a specific size.
Signed variants can hold numbers ranging from negative 2^n-1 to 2^n-1–1, where n is the number of bits used. For example, an i8 can store values from -128 to 127. On the other hand, unsigned variants can store numbers from 0 to 2^n-1. For instance, a u8 can represent values from 0 to 255.
The isize and usize types are determined by your computer’s architecture: 64 bits for a 64-bit system and 32 bits for a 32-bit system, as indicated in the table.
2. Floating-point:
In Rust, there are two types of floating-point numbers: f32 and f64, with sizes of 32 bits and 64 bits, respectively. The default type is f64, chosen for its precision on modern CPUs. Both f32 and f64 are signed, and f32 is a single-precision float, while f64 has double precision.
Here’s an example that demonstrates how floating-point numbers work:
let a= 6.0; // f64
let b: f32 = 8.0; // f32
3. Boolean:
Like in many programming languages, in Rust, a Boolean type has two possible values: true or false. Booleans in Rust use one byte of memory and are represented by the bool keyword. For instance:
// Example of a true boolean variable
let is_sunny = true;
// Example of a false boolean variable with explicit type annotation
let is_raining: bool = false;
4. Character Type:
In Rust, the char type is four bytes and can represent Unicode characters, not just ASCII. This includes accented letters, Chinese, Japanese, and Korean characters, emoji, and zero-width spaces. Here are examples of declaring char values:
let c = 'z';
let z: char = 'ℤ'; // with explicit type annotation
let heart_eyed_cat = '😻';
Compound Types
Compound types can group multiple values into one type. Rust has tuples and arrays as primitive compound types.
- Tuple:
A tuple is like a collection that holds different types of values together in a fixed order. Once we make a tuple, its size can’t change. To create one, we list values inside parentheses with commas between them. Each position in the tuple has a specific type, and these types can be different. Optional type annotations can be included if needed.
let tup: (i32, f64, u8) = (500, 6.4, 1);
The variable “tup” holds the entire tuple because a tuple is treated as one complete unit. To access individual values from the tuple, we use pattern matching to break down the tuple, like this:
let tup = (500, 6.4, 1);
let (x, y, z) = tup;
println!("The value of y is: {y}");
In this program, a tuple is made and assigned to the variable tup. It then uses a pattern with let to extract three individual variables, namely x, y, and z, from the tuple. This process, called destructuring, splits the single tuple into three parts. The program concludes by printing the value of y.
Alternatively, we can directly access a specific tuple element using a period (.) followed by the index of the desired value. For instance:
let x: (i32, f64, u8) = (500, 6.4, 1);
let five_hundred = x.0;
let six_point_four = x.1;
let one = x.2;
This program makes a group of values called a tuple, named x. It then grabs each part of the tuple by their order, starting from 0, just like in many programming languages.
When a tuple doesn’t have any values, it’s called unit. This empty tuple is denoted by () and stands for a kind of emptiness. If an operation or function doesn’t give any other value, it silently gives back the unit value.
2. Array:
You can use an array to store multiple values in Rust. Unlike tuples, all elements in an array must be of the same type. Also, unlike arrays in some other languages, Rust’s arrays have a fixed length.
To specify the values in an array, you list them inside square brackets, separated by commas:
let a = [1, 2, 3, 4, 5];
Arrays are handy when you want your data stored on the stack or when you need a fixed number of elements. Unlike arrays, vectors from the standard library can change in size. If you’re unsure, go for a vector.
Arrays shine when you know the element count won’t change. For instance, if you’re listing months in a program, go for an array since you know it’ll always have 12 elements:
let months = ["January", "February", "March", "April", "May", "June", "July",
"August", "September", "October", "November", "December"];
Array types are denoted with square brackets containing the element type, a semicolon, and the element count:
let a: [i32; 5] = [1, 2, 3, 4, 5];
n this example, i32 is the element type, and 5 signifies the array has five elements.
You can initialize an array with the same value for each element, concise and clear:
let a = [3; 5];
The array called “a” will have 5 elements, and they’ll all start with the value 3. This is like saying “let a = [3, 3, 3, 3, 3];” but shorter.
To get elements from an array, you use indexing, like this:
let a = [1, 2, 3, 4, 5];
let first = a[0];
let second = a[1];
In this example, the variable “first” gets the value 1 from the array’s first position (index [0]), and the variable “second” gets the value 2 from the array’s second position (index [1]).