DEV Community

Dipankar Paul
Dipankar Paul

Posted on

Understanding Data Types in Rust

In Rust, every value has a specific data type that tells the compiler how to work with that value. Rust is a statically typed language, meaning that the type of every variable must be known at compile time. Rust provides several built-in data types, which can be broadly categorized into scalar and compound types. Let's explore these types in more detail.

Scalar Types

A scalar type represents a single value. Rust has four primary scalar types: integers, floating-point numbers, Booleans, and characters. You may recognize these from other programming languages.

Integer Type

  • An integer is a number without a fractional component.
  • The default integer type is i32.
  • Signed integers: i8, i16, i32, i64, i128, isize
  • Unsigned integers: u8, u16, u32, u64, u128, usize
  • Each variant can be either signed(+/-) or unsigned(+) and has an explicit size.

Example:

let integer: i32 = 42;
let unsigned_integer: u64 = 100;
Enter fullscreen mode Exit fullscreen mode
  • Each signed variant can store numbers from -(2n - 1) to 2n - 1 - 1 inclusive, where n is the number of bits that variant uses.
  • Unsigned variants can store numbers from 0 to 2n - 1.
  • The isize and usize types depend on the architecture of the computer your program is running on.

Floating-Point Types

  • f32: 32-bit floating point
  • f64: 64-bit floating point, default.

Example:

let float: f64 = 3.14;
Enter fullscreen mode Exit fullscreen mode
  • Floating-point numbers are represented according to the IEEE-754 standard. The f32 type is a single-precision float, and f64 has double precision.

Boolean Type

  • bool: Represents true or false values.
  • Booleans are one byte in size.

Example:

let is_true: bool = true;
Enter fullscreen mode Exit fullscreen mode

Character Type

  • char: Represents a single Unicode character.
  • We can also store special characters like $,@ and &, etc. using the character type.

Example:

let character: char = 'A';
Enter fullscreen mode Exit fullscreen mode

Notably, we can also store numbers as characters using single quotes. For example,

let num_char: char = '5';

Here 5 is a character not a number.

Compound Types

Compound types can group multiple values into one type. Rust has two primitive compound types: tuples and arrays.

Array Type

  • Fixed-size, homogeneous data structure.
  • Every element of an array must have the same type.
  • Arrays are useful when you want your data allocated on the stack rather than the heap or when you want to ensure you always have a fixed number of elements.

Example:

let a: [i32; 3] = [1, 2, 3];

let first = a[0];  // Accessing array element
let second = a[1];
Enter fullscreen mode Exit fullscreen mode
  • Here, i32 is the type of each element. After the semicolon, the number 3 indicates the array contains three elements.
  • You can also initialize an array to contain the same value for each element.

Example:

let numbers = [3; 5];

println!("numbers = {:?}", numbers);  // [3, 3, 3, 3, 3]
Enter fullscreen mode Exit fullscreen mode

The println! macro asks for the debug implementation with the ? format parameter. {} gives the default output, {:?} gives the debug output. Types such as integers and strings implement the default output, but arrays only implement the debug output. This means that we must use debug output here.

Adding #, eg {numbers:#?}, invokes a “pretty printing” format, which can be easier to read.

Mutable Array in Rust

In Rust, an array is immutable, which means we cannot change its elements once it is created.

However, we can create a mutable array by using the mut keyword before assigning it to a variable.

Example:

fn main() {
    let mut numbers: [i32; 5] = [1, 2, 3, 4, 5];

    println!("original array = {:?}", array); // [1, 2, 3, 4, 5]

    // change the value of the 3rd element in the array
    numbers[2] = 0;

    println!("changed array = {:?}", numbers); // [1, 2, 0, 4, 5]
}
Enter fullscreen mode Exit fullscreen mode

We changed the element at index 2 (third element) from 3 to 0. This is possible because we have created the numbers array as mutable.

Note: Values inside an array can only be modified but cannot be deleted because the size of the array is fixed after initialization.

Tuple Type

  • Tuples group together elements of different types into one compound type.
  • Fixed-size, heterogeneous data structure.
  • Oonce declared, they cannot grow or shrink in size.

Example:

let tup: (i32, f64, &str) = (42, 3.14, "tuple");
Enter fullscreen mode Exit fullscreen mode
  • We can break down tuples into smaller variables, known as destructuring.

Example:

let (x, y, z) = tup;

println!("The value of y is: {y}");  // prints 3.14
Enter fullscreen mode Exit fullscreen mode

Destructuring a tuple is also known as tuple unpacking.

  • We can also access a tuple element directly by using a period (.) followed by the index of the value.

Example:

let first = tup.0;  // 42
let second = tup.1; // 3.14

println!("The value of y is: {y}");
Enter fullscreen mode Exit fullscreen mode

The tuple without any values has a special name, unit. This value and its corresponding type are both written () and represent an empty value or an empty return type. Expressions implicitly return the unit value if they don’t return any other value.

  • You can think of it as void that can be familiar to you from other programming languages.

Mutable Tuple

In Rust, a tuple is immutable by default, which means we cannot change its elements once it is created.

However, we can create a mutable array by using the mut keyword before assigning it to a variable.

Example:

fn main() {
    // initialize a mutable tuple
    let mut mountain_heights = ("Everest", 8848, "Fishtail", 6993);

    println!("Original tuple = {:?}", mountain_heights);
    // prints => ("Everest", 8848, "Fishtail", 6993)

    // change 3rd and 4th element of a mutable tuple
    mountain_heights.2 = "Lhotse";
    mountain_heights.3 = 8516;

    println!("Changed tuple = {:?}", mountain_heights);
    // prints => ("Everest", 8848, "Lhotse", 8516)
}
Enter fullscreen mode Exit fullscreen mode

Here, we create a mutable tuple named mountain_heights. We then change its 2nd and 3rd tuple index.

Note: You can only change the element of a tuple to the same type as when it was created. Changing data types is not allowed after tuple creation.

Some other types

We wiil learn about them in detail later.

String Type

  • Represents a sequence of characters.
  • The default string type is a string slice &str.
  • Strings can be either string slices or owned strings String.
  • String slices are often used for string literals, while String is used for dynamically allocated strings.

Example:

let string_slice: &str = "Hello, Rust!";
let owned_string: String = String::from("Rust is awesome!");
Enter fullscreen mode Exit fullscreen mode

Slice Type

  • Represents a view into a portion of a contiguous sequence.
  • Slice must reference an existing array or slice.
  • Slices are dynamically sized and provide a flexible way to work with arrays.

Example:

let array = [1, 2, 3, 4, 5];
let slice: &[i32] = &array[1..4]; // includes elements from index 1 to 3

// Accessing the elements of the slice
for num in slice {
   println!("Number: {}", num);
}   
Enter fullscreen mode Exit fullscreen mode

Reference Types

  • References allow you to borrow values without taking ownership.
  • Reference must be explicitly created by borrowing.
  • &T: Immutable reference, used for read-only access.
  • &mut T: Mutable reference, used for read-write access.

Example:

let x = 42;
let reference: &i32 = &x;
let mutable_reference: &mut i32 = &mut x;
Enter fullscreen mode Exit fullscreen mode
  • Rules for reference:
    • Multiple immutable references to the same data are allowed.
    • Only one mutable reference to a piece of data is allowed within a specific scope.

Example:

let x = 10;
let mut y = 10;

// Creating an immutable reference to x
let reference: &i32 = &x;

// Creating a mutable reference to y
let mutable_reference: &mut i32 = &mut y;

// Modifying the value through the mutable reference
*mutable_reference += 5;

// Accessing the value through the reference
println!("Value through immutable reference: {}", *reference);  // 10
println!("Value through mutable reference: {}", y);  // 15
Enter fullscreen mode Exit fullscreen mode
  • The * operator is used to dereference the reference and access the underlying value.

Conclusion

Understanding data types in Rust is crucial for writing safe and efficient code. By leveraging the different types provided by Rust, you can express your program's logic more clearly and effectively. Scalars and compounds are the building blocks of Rust programs, and mastering them will set you on the path to becoming a proficient Rust programmer.

Top comments (1)

Collapse
 
kapaseker profile image
PangoSea

👍