We have seen how variables are created in Rust using the let keyword. But what kind of variables can we create? Let’s explore them. Remember to run cargo run
every time you update the code.
Variables
Rust supports the mutability and immutability of variables. If this sounds alien to you, it simply means that with Rust, you can make a variable have the ability to change its value or not change it during compile time. Let’s see this more practically.
The variable x
above is generally a number. This variable cannot be changed throughout this code, so this won’t work:
fn main() -> () {
let x = 5;
println!("The value of x is: {}", x);
x = 10;
println!("The value of x is: {}", x);
}
The only way it will work is if we reassign x
or copy the value into another variable:
fn main() -> () {
let x = 5;
println!("The value of x is: {}", x);
let x = 10;
println!("The value of x is: {}", x);
}
Like this, the second instance of the variable x
is different from the first. But then, you had to write two more lines of code to achieve this change. To avoid this, we can simply use the mut
keyword, which stands for mutable when creating x
:
fn main() -> () {
let mut x = 5;
println!("The value of x is: {}", x);
x = 10;
println!("The value of x is now: {}", x);
}
This works, and the compiler doesn’t panic like it did before.
Now, let’s move on to data types.
Data types
Note: If you have configured your IDE or code editor to have the Rust plugin, you must have seen that a type inference was appended to x
:
This was done by the rust-analyzer and means that you can write the code as:
fn main() -> () {
let mut x: i32 = 5;
println!("The value of x is: {}", x);
x = 10;
println!("The value of x is now: {}", x);
}
Sometimes, we will need to specify the exact type of a variable to avoid anomaly behavior in our code.
Note: Rust has a reference that is being released every six weeks. It is the primary reference you should run to when you need to remember something, and it is available here. For example, you can find all the types available in Rust here. You can click on each link to find the subtypes under them.
Numeric types
Under Numeric types in the Primitive types collection, you should find Integer types, Floating-point types, and Machine-dependent integer types. You can also learn how types are specified. The i32
type we used above is for integers between 2^32 - 1. You have seen how to create numeric typed variables with and without type inference. Let’s learn how to do so in other types.
Textual types
Under Textual types, you should find characters char
and strings str
. They are created like this:
fn main() -> () {
let single_alphabet = 'a';
println!("The single character is: {}", single_alphabet);
let second_alphabet: char = 'b';
println!("The single character with type inference is: {}", second_alphabet);
}
When we explore strings in Rust, you will be introduced to memory management, an intermediate aspect of Rust. Nevertheless, let’s explore how to create string variables in Rust:
fn main() -> () {
let my_string = "Hello World";
println!("The string content is: {}", my_string);
let second_string: &str = "Hello, other world";
println!("The string content with type inference is: {}", second_string);
}
Notice the ampersand (&) we used during the type inferred variable second_string
. This is how strings in Rust work. If you create a string without it, the compiler will panic, and this is because Rust understands that strings can have a dynamic length during compile time, so it gets stored in a heap.
The non-complicated way to create strings in Rust is to use the automatic dynamically sized String
keyword:
fn main() -> () {
let another_string = String::from("Hello, world");
println!("The string content is: {}", another_string);
let new_string: String = String::from("Hey, world!");
println!("The type inferred string content is: {}", new_string);
}
From the Rust reference, you should take your time playing with the other Rust types. Let’s move on to the next section.
Sequence types
In Rust, sequential types refer to data structures that store a sequence of values in a specific order. These types allow you to store and manipulate collections of items. Rust provides several built-in sequential types with characteristics and uses cases. Here are some examples:
Arrays: Arrays in Rust have a fixed size determined at compile time and contain elements of the same type. They are stored on the stack and are useful when you need a fixed number of elements with a known size.
// Declaration and initialization of an array let numbers: [i32; 5] = [1, 2, 3, 4, 5];
Slices: Slices are references to a contiguous sequence of elements within another sequential type (like an array or a vector). They allow you to work with a portion of the data without copying it.
let numbers = [1, 2, 3, 4, 5]; let slice: &[i32] = &numbers[1..4]; // Slice containing elements 2, 3, and 4
Vectors: Vectors are dynamic arrays that can grow or shrink in size. They are stored on the heap and allow you to store various elements.
// Creating and modifying a vector let mut vec = Vec::new(); vec.push(1); vec.push(2); vec.push(3)
Strings: Rust's
String
type is a dynamically sized, UTF-8 encoded string. It is implemented as a vector of bytes and provides a convenient way to work with text.// Creating and manipulating strings let mut text = String::from("Hello, "); text.push_str("world!");
Ranges: Ranges are sequential types representing a sequence of values from a start value to an end value. They are often used in loops to iterate over a range of numbers.
for number in 1..=5 { // Inclusive range (1 to 5 inclusive) println!("Current number: {}", number); }
Tuples:
Tuples are collections of values of different types, and they have a fixed size that’s determined at compile time. Each element of a tuple can have its type.
// Creating tuples let person: (String, i32, bool) = ("Alice".to_string(), 30, true); // Accessing tuple elements let name = person.0; // Access the first element (name) let age = person.1; // Access the second element (age) let is_adult = person.2; // Access the third element (is_adult)
These sequential types in Rust provide various options for storing and manipulating data collections. Depending on your use case and requirements, you can choose the appropriate type to effectively manage your data and perform operations efficiently.