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
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 floating-point: f64
|
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() {
let variable = 2;
println!( "{}" , variable);
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() {
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 |
- i8, i16, i32, i64, and i128 are signed integer types in Rust, where the first number indicates the number of bits used to represent the integer. For example, i8 uses 8 bits and can represent integers from -128 to 127.
- isize is a signed integer type with a size that depends on the architecture of the system it’s running on (32 bits or 64 bits).
|
Unsigned integer types
|
- u8, u16, u32, u64, and u128, u stands for Unsigned making the integers only represent non-negative numbers. For example, u8 uses 8 bits and can represent integers from 0 to 255, if we try to give the Unsigned type a negative Rust will generate an error.
- usize like isize but Unsigned (32 bits or 64 bits).
|
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() {
let int : i8 = i8::MAX;
println!( "i8 : {}" , int );
let int : i16 = i16::MAX;
println!( "i16 : {}" , int );
let int : i32 = i32::MAX;
println!( "i32: {}" , int );
let int : i64 = i64::MAX;
println!( "i64 : {}" , int );
let int : i128 = i128::MAX;
println!( "i128 : {}" , int );
let int : isize = isize::MAX;
println!( "isize : {}" , int );
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() {
let mut float : f32 = 1.00000762;
float += 1.00000762;
println!( "{}" , float );
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() {
let is_true: bool = true ;
println!( "I'm reading a GFG article : {}" , is_true);
let is_false: bool = false ;
println!( "I go outside : {}" , is_false);
if is_true {
println!( "The condition is true!" );
} else {
println!( "The condition is false!" );
}
if !is_true {
println!( "The condition is false!" );
} else {
println!( "The condition is true!" );
}
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() {
let c: char = 'C' ;
println!( "the character is: {}" , c);
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() {
let text = String::from( "A random text" );
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() {
let tup: (i32, &str, f32) = (500, "This a immutable string" , 0.32);
println!( "{:?}" , tup);
let tup: (i32, String, f32) = (500, String::from( "This a mutable string" ), 0.32);
println!( "{:?}" , tup);
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() {
let array = [1, 2, 3, 4, 5];
println!( "{:?}" , array);
let array: [i32; 5] = [1, 2, 3, 4, 5];
println!( "{:?}" , array);
let array = [ "this will be my default value" ; 5];
println!( "{:?}" , array);
let array: [&str; 5] = [ "default" ; 5];
println!( "{:?}" , array);
let mut array = array;
array[0] = "can" ;
array[1] = "edit" ;
array[2] = "elements" ;
array[3] = "this" ;
array[4] = "way" ;
println!( "{:?}" , array);
}
|
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.
Last Updated :
01 May, 2023
Like Article
Save Article
Share your thoughts in the comments
Please Login to comment...