Primitive Variables in Rust
Primitive types are types that are already defined in the language most authors refer to as data types which is the most common reference. They serve as the building blocks for more complex data structures and types.
In Rust, the following are the primitive types:
Primitive Data Types | Description |
---|---|
Integer types | Signed integers: i8, i16, i32, i64, i128, and isize Unsigned integers: u8, u16, u32, u64, u128, and usize |
Floating-point types |
floating-point: f32 |
Boolean type |
bool (which values true and false) |
Character type |
char (represents a Unicode scalar value) |
Tuple types |
Tuples are fixed-size collections of heterogeneous elements, such as (i32, f64, and bool). |
Array types
|
Arrays are fixed-size collections of homogeneous elements, such as [i32; 5
|
Example 1:
Rust
fn main() { // we use let to declare a variable let variable = 2; println!( "{}" , variable); // we use let with mut to make the variable mutable let mut variable = variable; println!( "{}" , variable); variable = 5 * 2; println!( "{}" , variable); } |
Output:
2 2 10
Constants
While variables declared with the let keyword make them immutable, there is another separate const keyword for declaring constants. Constants are similar to immutable variables when we compare immutability, but they have some additional features assigned and restrictions, so here are the rules:
- Constants must have an explicit type annotation
- Constants can only be initialized with constant expressions, meaning they must have a value that can be determined at compile time. Runtime calculations and function calls are not allowed for initializing constants.
- Constants have a global scope, which means you can use them throughout your entire program, not just within the scope where they are declared.
- Constants cannot be bound to mut like let allows.
Example 2:
Rust
fn main() { // this is a constant in rust, const cannot be bound to a mut state const PI: f32 = 3.14; println!( "{}" , PI); } |
Output:
3.14
Integers
Integer types in Rust and their maximum values. Let’s take a look at the types of integers that Rusts offers.
Integer Type | Description |
---|---|
Signed integer types |
|
Unsigned integer types |
|
Signed integers are in most programming languages, and signed integer types are more common than unsigned integer types. The default integer type in many programming languages, such as Java, C#, Python, and Kotlin, is a 32-bit signed integer.
Using signed integers allows developers to work with a wider range of values without having to worry about the limitations of unsigned integers, which can only represent NON-negative values. However, unsigned integer types have their use cases, like interacting with array indexes, we can only represent NON-negative numbers.
Example 3:
Rust
fn main() { // types of int with their max values // i8 is same as Byte type in java, kotlin, C# and others let int : i8 = i8::MAX; println!( "i8 : {}" , int ); let int : i16 = i16::MAX; println!( "i16 : {}" , int ); // i32 is the default Int in most programing languages let int : i32 = i32::MAX; println!( "i32: {}" , int ); // i64 is same size as Long in other programing languages let int : i64 = i64::MAX; println!( "i64 : {}" , int ); let int : i128 = i128::MAX; println!( "i128 : {}" , int ); // isize is bound to the system. Eg :: (32 bits or 64 bits) let int : isize = isize::MAX; println!( "isize : {}" , int ); // the unsigned ints are integers which variable can only receive NO-negatives let uint: u8 = u8::MAX; println!( "u8 : {}" , uint); let uint: u16 = u16::MAX; println!( "u16 : {}" , uint); let uint: u32 = u32::MAX; println!( "u32 : {}" , uint); let uint: u64 = u64::MAX; println!( "u64 : {}" , uint); let uint: u128 = u128::MAX; println!( "u128 : {}" , uint); let uint: usize = usize::MAX; println!( "usize : {}" , uint); } |
In the code above, we can see all the integer types in Rust with their:: Max values assigned, and in the output, we can see that the value of unsigned is doubled because it doesn’t save bits to represent negatives and this allows these types to represent a large variety of positive aspects.
Output:
i8 : 127 i16 : 32767 i32: 2147483647 i64 : 9223372036854775807 i128 : 170141183460469231731687303715884105727 isize : 9223372036854775807 u8 : 255 u16 : 65535 u32 : 4294967295 u64 : 18446744073709551615 u128 : 340282366920938463463374607431768211455 usize : 18446744073709551615
Float
In Rust, there are two floating-point types: f32 and f64. These represent 32-bit single-precision and 64-bit double-precision floating-point numbers, respectively. The f64 type is more precise and has a larger range, making it the default floating-point type in Rust.
Example 4:
Rust
fn main() { // types of floats with their max values // f32 occupies 32 bits of memory and less precise than f64 let mut float : f32 = 1.00000762; float += 1.00000762; println!( "{}" , float ); // f64 occupies 64 bits of memory and is more precise let mut float : f64 = 1.00000762; float += 1.00000762; println!( "{}" , float ); } |
We can see from the different output when loading the variable, while f64 allows us to have more precision in the decimals, f32 rounds the value to 7 decimals, so if the program only needs 0.2 to 0.5 decimals we can use f32, if it needs 0.5 to 0.15 decimal, an f64 type handles this better.
Output:
2.0000153 2.00001524
Boolean
Boolean in Rust is like in most other programming languages, it has only two possible values, true or false which are reserved keywords for Boolean types. You can use Boolean to check conditions, it stores comparisons between variables, by default when checking conditions in languages they always check if it has a true value if you want to check false just use ! in front of the variable.
Example 5:
Rust
fn main() { // declare a variable as a boolean let is_true: bool = true ; println!( "I'm reading a GFG article : {}" , is_true); // declare another boolean variable let is_false: bool = false ; println!( "I go outside : {}" , is_false); // using booleans in if condition // is_true it check if the boolean has a true value if is_true { println!( "The condition is true!" ); } else { println!( "The condition is false!" ); } // !is_true will check if the boolean has a false value if !is_true { println!( "The condition is false!" ); } else { println!( "The condition is true!" ); } // we can do any type of comparation and store the result in a boolean let a = 5; let b = 10; let is_greater: bool = a > b; println!( "Is {} greater than {}? {}" , a, b, is_greater); let c = 10.2; let d = 10.9; println!( "Is {} greater than {}? {}" , d, c, d > c); } |
Output:
I'm reading a GFG article : true The economy looks great : false The condition is true! The condition is true! Is 5 greater than 10? false
Char
Now let’s see the code demonstrates the use of the char type in Rust and how to store individual characters, including Unicode characters.
- let c: char = ‘A’; declares a variable named c of type char and assigns the character between two ‘ to it.
- let unicode_char: char = ‘\u{42}’; we can assign any Unicode character using \u{code}.
Example 6:
Rust
fn main() { // type a variable as a char let c: char = 'C' ; println!( "the character is: {}" , c); // unicode value for the character @ let unicode_char: char = '\u{40}' ; println!( "Unicoded: {}" , unicode_char); } |
Output:
the character is: C Unicoded: @
String
Before we take a look at Tuples and Arrays, we should take a quick look at a String, and since this type is built on top of a more complex data structure than the basic type, they are not considered primitive types.
In Rust, we have two types of String:
- The string is the type that is similar to other implementations used in another programing language. The String is the type that is mutable and allows manipulation of itself, we can increase the string remove elements, etc.
- &str is a reference to the s type of String, it references the above String type, when we use this type we are only allowed to read from the variable, we cannot modifier it, as it’s immutable.
The below code demonstrates the basic initialization of String and &str types in Rust.
Example 7:
Rust
fn main() { // String::from() create a new String like most other languages let text = String::from( "A random text" ); // &str is a string type immutable, a reference to the String type let name: &str = "Any" ; println!( "{name}" ); println!( "{text}" ); } |
Output:
Any A random text
Tuples
Now tuple is a general way of grouping together a number of values with a variety of types into one compound type. Tuples have a fixed length: once declared, they cannot grow or shrink in size.
The signature starts let or const keywords, next step is to name and assign the elements in parentheses, so we have this signature: let <name> : (<define type before assign>) = (<assign elements separated by a comma, assign in order of types if you define type previous>)
Tuples we can read elements and edit them if declared with let mut only and with let if assign it to a new variable.
Here’s an example demonstrating how to initialize tuples in Rust, here we also used the print Debug formatter which is a println!(“{:?}”, tup), it’s shown on the console the elements inside a struct.
Infer type | Explicit type |
---|---|
let tup = (500, “A random text”, 0.32) | let tup: (i32, &str, f64) = (500, “A random text”, 0.32) |
Example 8:
Rust
fn main() { // tuples are way to store different types together let tup: (i32, &str, f32) = (500, "This a immutable string" , 0.32); println!( "{:?}" , tup); // you can replace &str for String to make the str mutable let tup: (i32, String, f32) = (500, String::from( "This a mutable string" ), 0.32); println!( "{:?}" , tup); // with a mut tuple we can edit like this let mut tup = tup; tup.0 = 10; tup.1 = String::from( "editing" ); tup.2 = 0.64; println!( "{:?}" , tup); } |
Output:
(500, "This a immutable string", 0.32) (500, "This a mutable string", 0.32) (10, "editing", 0.64)
Array
Another way to have a collection of multiple values is with an array. Unlike a tuple, every element of an array must have the same type.
The below code demonstrates different ways to create and initialize arrays in Rust.
- Initializing an array with elements.
- Initializing an array with explicit type, size, and elements.
- Initializing an array with a default value and size.
- Initializing an array with a default value, size, and explicit type.
Infer type array | Explicit type array | Infer type by default value | Explicit type and set the default value |
---|---|---|---|
let array = [1, 2, 3, 4, 5]; | let array: [i32; 5] = [1, 2, 3, 4, 5]; | let array = [1; 5]; | let array: [$str; 5] = [“default”; 5]; |
Example 9:
Rust
fn main() { // this is how you init a array = [elements] let array = [1, 2, 3, 4, 5]; println!( "{:?}" , array); // this is how you define the type of the // array [<type>; <size>] = [elements] let array: [i32; 5] = [1, 2, 3, 4, 5]; println!( "{:?}" , array); // this is how you init a array with a default :: //= [<type and default value>, <size>] let array = [ "this will be my default value" ; 5]; println!( "{:?}" , array); // this is same above array, but hand typed. [<type>; // <size>] = [<type and default value>, <size>] let array: [&str; 5] = [ "default" ; 5]; println!( "{:?}" , array); // with a mut array now let mut array = array; array[0] = "can" ; array[1] = "edit" ; array[2] = "elements" ; array[3] = "this" ; array[4] = "way" ; println!( "{:?}" , array); // remember when hand type, assign has to match the type } |
Output:
[1, 2, 3, 4, 5] [1, 2, 3, 4, 5] ["this will be my default value", "this will be my default value", "this will be my default value", "this will be my default value", "this will be my default value"] ["default", "default", "default", "default", "default"] ["can", "edit", "elements", "this", "way"]
Hand-Typing
Even though all the example code given here has an explicit type when creating the variable, Rust is also able to infer the type at compile time, which means we don’t have to give it an explicit type. But explicit typing provides clarity making the code easier to understand for other developers or even for yourself when you revisit the code later. It helps to understand the purpose and constraints of the variable, specifying the type also helps the compiler to ensure that the value assigned to the variable meets the constraints of the type specified in advance. That way, the compiler can catch potential type-related errors early in the development process.
Rust Types and Inference
Pre-requisites: Rust, Scalar Datatypes in Rust
Rust is a multi-paradigm programming language like C++ syntax that was designed for performance and safety, especially safe concurrency by using a borrow checker and ownership to validate references.
In this article, we will focus on Rust’s primitive types. By understanding the way to declare these types we will have a solid foundation to start with in Rust and later on, it will help with more advanced concepts.
- Integers (signed and unsigned).
- Floating-point numbers (f32 and f64).
- Booleans (bool).
- Characters (char).
- Strings (String and &str).
- Tuples.
- Arrays