Rust Data Types

Francesco Ciulla - Jan 23 - - Dev Community

Data types in Rust

Rust is a statically typed language, which means that it must know the types of all variables at compile time.

The compiler can usually infer what type we want to use based on the value and how we use it. In cases when many types are possible, we must add a type annotation.

In this lesson, we will cover the basic data types in Rust.

We will talk about:

  • Scalar types
  • Compound types
  • Custom types

If you prefer a video version

Scalar types

A scalar type represents a single value. Rust has four primary scalar types:

  • integers
  • floating-point numbers
  • Booleans
  • characters

Let's see them one by one.

Integer types

Rust offers a variety of integer types, differing in size and whether they are

  • signed (capable of representing negative numbers)
  • unsigned (only representing non-negative numbers).

The size of an integer type determines its range of values.

List of Integer Types

Below is a table listing all of Rust's integer types, along with their sizes in bits, whether they are signed, and their range of values:

Length Signed Unsigned
8-bit i8 u8
16-bit i16 u16
32-bit i32 u32
64-bit i64 u64
128-bit i128 u128
arch isize usize


fn main() {
    // Signed integers
    let small_signed: i8 = -128; // Smallest value for i8
    let large_signed: i64 = 9_223_372_036_854_775_807; // Largest value for i64

    // Unsigned integers
    let small_unsigned: u8 = 0; // Smallest value for u8
    let large_unsigned: u128 = 340_282_366_920_938_463_463_374_607_431_768_211_455; // Largest value for u128

    println!("Small signed: {}", small_signed);
    println!("Large signed: {}", large_signed);
    println!("Small unsigned: {}", small_unsigned);
    println!("Large unsigned: {}", large_unsigned);
}


Enter fullscreen mode Exit fullscreen mode

In this example:

We declare and initialize two signed integers (i8 and i64) and two unsigned integers (u8 and u128).
We assign them their minimum or maximum possible values, showcasing the range of each type.
Finally, we print these values to the console.

We have a similar example here:

Data Types in Rust - Rust programming tutorial

Integer Literals

In Rust, integer literals can be written in different numeral systems:

Numeral System Description Example
Decimal Base-10, common form 98_222
Hexadecimal Base-16, prefixed with 0x 0xff
Octal Base-8, prefixed with 0o 0o77
Binary Base-2, prefixed with 0b 0b1111_0000
Byte (u8 only) ASCII characters, prefixed with b b'A'

Here's an example demonstrating how to use different integer types and literals in Rust:



fn main() {
    let decimal: i32 = 98_222;    // Decimal
    let hex: u32 = 0xff;          // Hexadecimal
    let octal: u8 = 0o77;         // Octal
    let binary: u8 = 0b1111_0000; // Binary
    let byte: u8 = b'A';          // Byte (u8 only)

    println!("Decimal: {}", decimal);
    println!("Hexadecimal: {}", hex);
    println!("Octal: {}", octal);
    println!("Binary: {}", binary);
    println!("Byte: {}", byte);
}



Enter fullscreen mode Exit fullscreen mode

Here is a similar example:

Data Types in Rust - Rust programming tutorial

Floating-point types

Rust has two primary types for representing floating-point numbers: f32 and f64.

The f32 is a single-precision float, while f64 is a double-precision float.

The default type is f64 because it offers a good balance between precision and performance on modern CPUs. It's roughly the same speed as f32 but provides more precision.

This example shows how to declare floating-point variables and perform basic arithmetic operations.



fn main() {
    let x = 2.0; // f64, double-precision
    let y: f32 = 3.0; // f32, single-precision

    // Arithmetic operations
    let sum = x + y as f64; // Type casting f32 to f64
    let difference = x - y as f64;
    let product = x * y as f64;
    let quotient = x / y as f64;

    println!("Sum: {}", sum);
    println!("Difference: {}", difference);
    println!("Product: {}", product);
    println!("Quotient: {}", quotient);
}



Enter fullscreen mode Exit fullscreen mode

Here is a similar example:

Data Types in Rust - Rust programming tutorial

Boolean type

In Rust, the Boolean type is represented by bool. It is one byte in size and can only take two values: true and false.

Booleans are often used in conditional statements to control the flow of a program.



fn main() {
    let t = true;
    let f: bool = false; // Explicit type annotation

    // Using Booleans in an if statement
    if t {
        println!("t is true");
    }

    if !f { // using ! to invert the Boolean value
        println!("f is false");
    }
}


Enter fullscreen mode Exit fullscreen mode

In this example:

Data Types in Rust - Rust programming tutorial

Character type

In Rust, the char type is four bytes in size and is used to represent a single Unicode Scalar Value. This means it can encode much more than just ASCII characters.

It can represent a wide range of characters, including:

  • accented letters
  • characters from various languages (Chinese, Japanese, Korean, ...)
  • symbols like emojis and even zero-width spaces.

Unicode Scalar Values

Unicode Scalar Values in Rust range from U+0000 to U+D7FF and U+E000 to U+10FFFF, making char capable of representing over a million different characters.



fn main() {
    let c = 'z'; // ASCII character
    let z = '𝕏'; // Unicode character (U+1D54F)
    let heart_eyed_cat = '😻'; // Emoji

    // Iterating over characters in a string
    for char in "Hello, δΈ–η•Œ!" πŸš€.chars() {
        println!("{}", char);
    }
}


Enter fullscreen mode Exit fullscreen mode

In this example:

  • We declare three char variables: c, z, and heart_eyed_cat, showcasing the ability of char to store various types of characters.
  • We also demonstrate how to iterate over the characters of a string, which includes both ASCII and non-ASCII characters.

Rust's char type is versatile and essential for applications requiring handling diverse character sets, such as text processing in different languages.

Here is a similar example:

Data Types in Rust - Rust programming tutorial

Compound types

Compound types can group multiple values into one type.

Rust has two primitive compound types: tuples and arrays.

Tuple type

In Rust, a tuple is a versatile way to group together a number of values of varying types into one compound type. Tuples are particularly useful when you need to return multiple values from a function or when you want to pass around a group of values that might not be related enough to warrant a struct.

Characteristics

  • Fixed Length: A tuple's size cannot change once declared. This means you cannot add or remove elements from a tuple after its creation.
  • Heterogeneous: Tuples can contain elements of different types, making them more flexible than arrays for specific use cases.


fn main() {
    let tup: (i32, f64, u8) = (500, 6.4, 1);

    // Destructuring a tuple into individual variables
    let (x, y, z) = tup;
    println!("The value of y is: {}", y);

    // Accessing tuple elements directly by their index
    let five_hundred = tup.0;
    let six_point_four = tup.1;
    let one = tup.2;

    println!("Values: {}, {}, {}", five_hundred, six_point_four, one);
}


Enter fullscreen mode Exit fullscreen mode

Destructuring and Indexing

In the example above, we demonstrate two common ways to work with tuples:

  • Destructuring: This involves breaking a tuple down into its individual components. In the example, let (x, y, z) = tup; unpacks the tuple so each variable contains one of the tuple's values.
  • Direct Access: You can also access individual elements of a tuple using a period (.) followed by the value index, such as tup.0. This is useful for quickly grabbing a single value from a tuple.

Tuples are a fundamental compound type in Rust, providing a straightforward way to aggregate a fixed number of items of potentially different types into a single, cohesive unit.

Data Types in Rust - Rust programming tutorial

Array type

In Rust, an array is a collection of elements of the same type with a fixed length. Arrays in Rust are immutable by default and their size cannot be altered after they are declared. This makes arrays suitable for scenarios where you need a constant-size collection of elements.

Characteristics and Usage

  • Fixed Length: The size of an array is determined at compile time and cannot be changed. This offers predictability and efficiency in memory usage.
  • Stack Allocation: Arrays are allocated on the stack rather than the heap, which can be more efficient for small collections or fixed-size data structures.
  • Uniform Type: All elements in an array must be of the same type.


fn main() {
    let a = [1, 2, 3, 4, 5]; // Array of type [i32; 5]

    // Accessing elements
    let first = a[0]; // First element
    let second = a[1]; // Second element
    println!("First: {}, Second: {}", first, second);

    // Iterating over an array
    for element in a.iter() {
        println!("Value: {}", element);
    }
}


Enter fullscreen mode Exit fullscreen mode

Accessing and Iterating
In the provided example:

  • We create an array a with five integers.

  • Elements are accessed using index notation, such as a[0] for the first element.
    We use a for loop with .iter() to iterate over each element in the array, demonstrating how to access and manipulate array elements in a sequence.

  • Arrays are a fundamental part of Rust's type system, ideal for when you need a simple, fixed-size list of elements. For dynamic collections where the size can change, Rust offers other types like vectors (Vec). We will cover vectors in a later lesson.

Here is a similar example:

Data Types in Rust - Rust programming tutorial

Custom types

Rust allows you to define your own data types. You can define custom data types using the struct and enum keywords.

Struct type

A struct is a custom data type that lets you name and package together multiple related values that make up a meaningful group. Structs are similar to tuples, but with named fields. Structs are useful when you want to give a group of values a name and clarify your code's intent.



struct Person {
  name: String,
  age: u8,
}

fn main() {
  // Creating an instance of the struct
  let person = Person {
  name: String::from("Alice"),
  age: 30,
};

  // Accessing fields of the struct
  println!("Name: {}", person.name);
  println!("Age: {}", person.age);
}


Enter fullscreen mode Exit fullscreen mode

In this example:

  • We define a Person struct with two fields: name (of type String) and age (of type u8).
  • We then create an instance of the Person struct, initializing the fields with specific values.
  • The println! macro is used to display the values of the struct's fields.

Data Types in Rust - Rust programming tutorial

Enum type

Enums in Rust, short for enumerations, are powerful custom data type that allow you to define a type by enumerating its possible variants.

They are handy for creating a type that can be one of a few different things, each potentially with different types and amounts of associated data.

Key Features of Enums:

  • Variants: Enums can have multiple variants, and each variant can optionally carry different types and amounts of data.
  • Pattern Matching: Enums are often used with Rust's match control flow construct, which provides a way to execute different code based on the variant of the enum.
  • Common Use Cases: Enums are widely used for error handling (Result enum), optional values (Option enum), and state management.

Let's see this with an example. We also have a spoiler for you: one of my favorite features of Rust is the match statement, which we will cover in an upcoming lesson.



// Define an enum to represent the states of a traffic light
enum TrafficLight {
    Red,
    Yellow,
    Green,
}

fn main() {
    let light = TrafficLight::Red;

    match light {
        TrafficLight::Red => println!("Stop"),
        TrafficLight::Yellow => println!("Caution"),
        TrafficLight::Green => println!("Go"),
    }
}


Enter fullscreen mode Exit fullscreen mode

In this example, TrafficLight is an enum with three variants: Red, Yellow, and Green.

We create an instance of TrafficLight::Red and then use a match statement to perform different actions based on the variant.

Data Types in Rust - Rust programming tutorial

Recap

In this lesson, we covered the basic data types in Rust.

We learned about:

  • scalar types: integers, floating-point numbers, Booleans, and characters
  • compound types: tuples and arrays
  • custom types: structs and enums

EXTRA: print the type of a variable




fn main() {
    // Arrays
    let a = [1, 2, 3, 4, 5]; // type is [i32; 5]

    //print the type of a
    print_type_of(&a);
}

fn print_type_of<T>(_: &T) {
    println!("{}", std::any::type_name::<T>())
}


Enter fullscreen mode Exit fullscreen mode

If you prefer a video version

You can keep in touch with me here: Francesco Ciulla

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .