Data Types

Use Scalar Data Types

Rust by example - Primitives

Rust has several categories of primitive scalar types: integers, floating-point numbers, Booleans, and unicode characters.

Type FamilyTypesExamples
Signed Integersi8⮳, i16⮳, i32⮳, i64⮳, i128⮳, isize⮳.-8i8, -32i32.
Unsigned Integersu8⮳, u16⮳, u32⮳, u64⮳, u128⮳, usize⮳.6u8.
Floating pointf32⮳, f64⮳.0.15.
Booleanbool⮳.true, false.
Unicode Charactercharlet z: char = 'ℤ';

usize⮳ and isize⮳ are 32 or 64 bits, depending on the architecture of the computer.

The following illustrates the various scalar data types:

fn main() {
    // Integer types:
    let a: i8 = -128; // 8-bit signed integer.
    let b: u8 = 255; // 8-bit unsigned integer.
    let c: i16 = 32767; // 16-bit signed integer.
    let d: u16 = 65535; // 16-bit unsigned integer.
    let e: i32 = 2147483647; // 32-bit signed integer (default for integers).
    let f: u32 = 4294967295; // 32-bit unsigned integer.
    let g: i64 = 9223372036854775807; // 64-bit signed integer.
    let h: u64 = 18446744073709551615; // 64-bit unsigned integer.
    let i: i128 = 170141183460469231731687303715884105727; // 128-bit signed integer.
    let j: u128 = 340282366920938463463374607431768211455; // 128-bit unsigned integer.
    let k: isize = 92233720; // Pointer-sized signed integer (depends on architecture).
    let l: usize = 18446740; // Pointer-sized unsigned integer (depends on architecture).

    // Type inference.
    let m = 42; // The compiler infers `i32` by default.

    // Floating-point types.
    let n: f32 = 3.1; // 32-bit float.
    let o: f64 = 2.7; // 64-bit float (default for floats).

    // Boolean type.
    let p: bool = true;
    let q: bool = false;

    // Character type (Unicode scalar value).
    let r: char = 'a';
    let s: char = '😀'; // Unicode emoji.
    let t: char = '∞'; // Unicode symbol.

    // Print all values.
    println!("Integers:");
    println!("i8: {a}");
    println!("u8: {b}");
    println!("i16: {c}");
    println!("u16: {d}");
    println!("i32: {e}");
    println!("u32: {f}");
    println!("i64: {g}");
    println!("u64: {h}");
    println!("i128: {i}");
    println!("u128: {j}");
    println!("isize: {k}");
    println!("usize: {l}");
    println!("inferred i32: {m}");

    println!("\nFloating-point:");
    println!("f32: {n}");
    println!("f64: {o}");

    println!("\nBooleans:");
    println!("true: {p}");
    println!("false: {q}");

    println!("\nCharacters:");
    println!("ASCII: {r}");
    println!("Emoji: {s}");
    println!("Symbol: {t}");

    // Type operations:
    println!("\nType operations:");
    println!("Integer addition: {a} + {} = {}", 100, a.wrapping_add(100)); // Using `wrapping_add` to avoid overflow panic.
    println!("Float multiplication: {n} * {o} = {}", (n as f64) * o);
    println!("Boolean AND: {p} && {q} = {}", p && q);
    println!("Boolean OR: {p} || {q} = {}", p || q);
}

Declare and Use Compound Data Types: Tuples and Arrays

Rust by example - tuples Rust by example - array

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

TypeExamples
Tupleslet tup: (i32, f64, u8) = (500, 6.4, 1);. Access via let five_hundred = x.0;. Destructuring via let (x, y, z) = tup;.
Arrayslet a: [i32; 5] = [1, 2, 3, 4, 5];. Access via let first = a[0];.

Both are fixed length. A Vector is a similar collection type provided by the standard library that is allowed to grow or shrink in size.

The following provides examples of tuples and arrays:

/// Demonstrates the use of tuples and arrays in Rust, including
/// their declaration, access, destructuring, iteration, and mutation.
fn main() {
    println!("=== TUPLES ===");

    // Tuple with mixed types:
    // Tuples are fixed-size collections of values of potentially different
    // types.
    let person: (String, i32, bool) = (String::from("Alice"), 30, true);

    // Access tuple elements by index:
    // Tuple elements are accessed using a dot followed by the index
    // (starting from 0).
    println!("Name: {}", person.0);
    println!("Age: {}", person.1);
    println!("Active: {}", person.2);

    // Destructure a tuple:
    // Tuple elements can be extracted into separate variables.
    let (name, age, active) = person;
    println!("Destructured - Name: {name}, Age: {age}, Active: {active}",);

    // Nested tuples:
    // Tuples can contain other tuples, allowing for complex data structures.
    let complex_data = (("Coordinates", (10.5, 20.8)), 42, [1, 2, 3]);
    println!(
        "Nested tuple access: {}, {}",
        (complex_data.0).0,
        (complex_data.0).1.0
    );

    // Unit (empty tuple).
    // The unit type `()` is a special tuple with no elements.
    let empty: () = ();
    println!("Empty tuple: {empty:?}");

    // Tuple with a single element:
    // A single-element tuple requires a trailing comma to distinguish it from a
    // parenthesized expression.
    let single = (42,); // Note the comma.
    println!("Single element tuple: {single:?}");

    // Functions with tuples.
    // Tuples can be passed to and returned from functions.
    fn swap_tuple(tuple: (i32, i32)) -> (i32, i32) {
        (tuple.1, tuple.0)
    }

    let original = (10, 20);
    let swapped = swap_tuple(original);
    println!("\nOriginal tuple: {original:?}");
    println!("Swapped tuple: {swapped:?}");

    println!("\n=== ARRAYS ===");

    // Fixed-size array with explicit type:
    // Arrays are fixed-size collections of elements of the same type.
    let numbers: [i32; 5] = [1, 2, 3, 4, 5];

    // Array with repeated values:
    // Arrays can be initialized with a repeated value
    // using the `[value; size]` syntax.
    let _zeros = [0; 10]; // Creates [0, 0, 0, 0, 0, 0, 0, 0, 0, 0].

    // Array elements are accessed using square brackets
    // and the index (starting from 0).
    println!("First element: {}", numbers[0]);
    println!("Last element: {}", numbers[4]);

    // The `len()` method returns the number of elements in the array.
    println!("Array length: {}", numbers.len());

    // Slices from an array:
    // Slices provide a view into a contiguous sequence of elements in an array.
    let slice = &numbers[1..4]; // [2, 3, 4].
    println!("Slice: {slice:?}");

    // Array of tuples:
    // Arrays can contain tuples, allowing for structured data within an array.
    let pairs: [(i32, &str); 3] = [(1, "one"), (2, "two"), (3, "three")];

    // Iterating over an array:
    // The `iter()` method provides an iterator over the array elements.
    println!("Iterating over array:");
    for num in numbers.iter() {
        println!("  Value: {num}");
    }

    // The `enumerate()` method provides both the index
    // and the value for each element.
    println!("Iterating with index:");
    for (i, num) in numbers.iter().enumerate() {
        println!("  numbers[{i}] = {num}");
    }

    // Iterating over an array of tuples allows access
    // to both parts of each tuple.
    println!("Iterating over array of tuples:");
    for (number, name) in pairs.iter() {
        println!("  {number} is written as {name}");
    }

    // Multi-dimensional array:
    // Arrays can be nested to create multi-dimensional arrays.
    let matrix: [[i32; 3]; 2] = [[1, 2, 3], [4, 5, 6]];

    println!("Multi-dimensional array:");
    for row in matrix.iter() {
        println!("  {row:?}");
    }

    // Arrays can be mutated if declared with `mut`.
    let mut mutable_array = [1, 2, 3, 4, 5];
    mutable_array[2] = 99;
    println!("Mutated array: {mutable_array:?}");
}

Declare Strings

Primitive type str is a string slice that represents a view into a string, allowing you to access a portion of a string without owning it. It is immutable and typically used as a reference type, denoted as &str, which allows for efficient borrowing of string data. Its memory can be on the heap, stack, or static. String slices must always be valid UTF-8. &'static str is the type of string literals.

String is a growable, mutable, owned string allocated on the heap. It is not a primitive type, but is rather part of the standard library.

&String can be coerced to &str, which makes &str a candidate for function arguments, if mutability and ownership are not required. If mutation is needed, use &mut String.

fn main() {
    // Basic string types:
    let u: &str = "Hello"; // String slice.
    let v: String = String::from("World"); // Owned string.

    println!("String slice: {u}");
    println!("Owned string: {v}");

    println!(
        "String concatenation: {} + {} = {}",
        u,
        v,
        u.to_string() + &v
    );
}

Strings are covered in much more details in the Strings and Text Processing chapters.

Use the Unit and Never Special Types

Type FamilyTypesExamples
Unitunit⮳.The () type (aka 'void' in other languages) has exactly one value (), and is used when there is no other meaningful value that could be returned.
Nevernever⮳.! represents the type of computations which never resolve to any value at all. For example, the exit function fn exit(code: i32) -> ! exits the process without ever returning, and so returns !.

See also the Functions chapter.

// The unit type is used when there is nothing to return:
#[allow(clippy::unused_unit)]
fn a_func() -> () {
    println!("a_func was called.");
}

// `()` is most commonly seen implicitly.
// Functions without a `-> ...` implicitly have return type `()`.
// The following is equivalent to the above:
fn b_func() {
    println!("b_func was called.");
}
// The semicolon ; discards the result of an expression at the end of a block,
// so that the block returns `()`.

// The `!` type, also called "never", represents the type of computations which
// never resolve to any value at all.
fn diverge() -> ! {
    panic!("This function never returns!");
}
// This function could also contain an infinite loop,
// or call the `exit` function.

fn main() {
    a_func();
    b_func();
}

Declare a Type Alias

A type alias is a way to give a new name to an existing type, making code easier to read and write. It does not create a new type, meaning the original type's properties still apply.

Use the type keyword to declare type aliases:

type Kilometers = u32;

type Point = (i32, i32);

// With generics:
type TypeAlias<T> = Bar<T> where T: Foo;

Related Topics

See also:

  • Enums.
    • Option.
    • Result.
  • Generics.
  • Slices.
  • Strings.
  • Structs.
  • Vectors.
  • Data Structures.