Data Types
Use Scalar Data Types |
Declare and Use Compound Data Types: Tuples and Arrays |
Use the Unit and Never Special Types |
Declare Strings |
Declare a Type Alias |
Use Scalar Data Types
Rust has several categories of primitive scalar types: integers, floating-point numbers, Booleans, and unicode characters.
Type Family | Types | Examples |
---|---|---|
Signed Integers | i8 ⮳, i16 ⮳, i32 ⮳, i64 ⮳, i128 ⮳, isize ⮳. | -8i8 , -32i32 . |
Unsigned Integers | u8 ⮳, u16 ⮳, u32 ⮳, u64 ⮳, u128 ⮳, usize ⮳. | 6u8 . |
Floating point | f32 ⮳, f64 ⮳. | 0.15 . |
Boolean | bool ⮳. | true , false . |
Unicode Character | char ⮳ | let 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
Compound types can group multiple values into one type. Rust has two primitive compound types: tuples and arrays.
Type | Examples |
---|---|
Tuples | let tup: (i32, f64, u8) = (500, 6.4, 1); . Access via let five_hundred = x.0; . Destructuring via let (x, y, z) = tup; . |
Arrays | let 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 Family | Types | Examples |
---|---|---|
Unit | unit ⮳. | 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. |
Never | never ⮳. | ! 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.