Data Types in Rust

Rust is a statically typed language, which means it must know the types of all variables at compile time. This guide covers the various data types available in Rust.

Scalar Types

Scalar types represent a single value. Rust has four primary scalar types:

Integers

Integers are numbers without fractional components. Rust provides a variety of integer types with different sizes:

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

The isize and usize types depend on the architecture of the computer your program is running on:

  • 64 bits on 64-bit architecture
  • 32 bits on 32-bit architecture

Integer literals can be written in various formats:

fn main() {
    let a = 98_222;    // Decimal
    let b = 0xff;      // Hex
    let c = 0o77;      // Octal
    let d = 0b1111_0000; // Binary
    let e = b'A';      // Byte (u8 only)
}

The underscore (_) can be used as a visual separator for readability.

Floating-Point Numbers

Rust has two floating-point types: f32 and f64. The default is f64 because on modern CPUs, it's roughly the same speed as f32 but offers more precision.

fn main() {
    let x = 2.0;      // f64 (default)
    let y: f32 = 3.0; // f32

    // Basic operations
    let sum = 5.0 + 10.0;     // addition
    let difference = 95.5 - 4.3;  // subtraction
    let product = 4.0 * 30.0;     // multiplication
    let quotient = 56.7 / 32.2;   // division
    let remainder = 43.0 % 5.0;   // remainder
}

Booleans

A boolean type has two possible values: true and false. Booleans are one byte in size and are often used in conditional expressions.

fn main() {
    let t = true;
    let f: bool = false;

    // Booleans are often used in conditionals
    if t {
        println!("This will be printed");
    }

    if f {
        println!("This will not be printed");
    }
}

Characters

Rust's char type is the language's most primitive alphabetic type. It represents a Unicode Scalar Value, which means it can represent a lot more than just ASCII.

fn main() {
    let c = 'z';
    let z: char = 'ℤ';         // Unicode character
    let heart_eyed_cat = '😻'; // Unicode emoji

    println!("Character size: {} bytes", std::mem::size_of::<char>());  // 4 bytes
}

Note that char literals use single quotes, while string literals use double quotes.

Compound Types

Compound types can group multiple values into one type. Rust has several compound types:

Tuples

A tuple is a general way of grouping together a number of values with a variety of types into one compound type with a fixed length.

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

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

    // Access via index
    let five_hundred = tup.0;
    let six_point_four = tup.1;
    let one = tup.2;
}

The tuple without any values, (), is a special type called the unit type. This value and its corresponding type are both written () and represent an empty value or an empty return type.

Arrays

Arrays in Rust have a fixed length and every element must have the same type:

fn main() {
    let a = [1, 2, 3, 4, 5];

    // Arrays with explicit type and size
    let a: [i32; 5] = [1, 2, 3, 4, 5];

    // Creating an array with the same value repeated
    let a = [3; 5]; // Equivalent to [3, 3, 3, 3, 3]

    // Accessing array elements
    let first = a[0];
    let second = a[1];

    // Arrays have bounds checking at runtime
    // let invalid = a[10]; // This would cause a runtime panic
}

Unlike some low-level languages, Rust checks for array bounds at runtime, which helps prevent buffer overflow vulnerabilities.

Slices

Slices are similar to arrays but their size is not known at compile time. A slice is a reference to a contiguous sequence of elements in a collection:

fn main() {
    let a = [1, 2, 3, 4, 5];

    let slice = &a[1..3]; // This is a slice containing elements 2 and 3

    println!("The first element of the slice is: {}", slice[0]); // 2
}

Slices are a view into the original data and don't take ownership of the data they reference.

Strings

Rust has two string types:

  • String: a growable, heap-allocated data structure
  • &str: a string slice, often used as a view into a String
fn main() {
    // String type (owned)
    let mut s = String::from("hello");
    s.push_str(", world!");
    println!("{}", s);

    // String slice (borrowed)
    let hello = "Hello, world!";
    let slice = &hello[0..5]; // "Hello"
    println!("{}", slice);
}

Type Conversion

Rust requires explicit type conversion (casting) between numeric types:

fn main() {
    let x = 5;
    let y = 3.0;

    // This won't compile:
    // let sum = x + y;

    // Instead, do this:
    let sum = x as f64 + y;

    // Or convert the float to an integer (truncates the decimal part)
    let truncated = y as i32 + x;
}

For more complex conversions between types, Rust provides the From and Into traits:

fn main() {
    let s = String::from("hello"); // Convert &str to String

    let num_str = String::from("42");
    let num: i32 = num_str.parse().expect("Not a number!"); // Convert String to i32
}

Custom Types

Rust allows you to define your own types using structs and enums, which we'll cover in other guides.