Skip to Content
Data Types

Data Types in Rust

Rust is a statically typed programming language — like C, C++, and Java. Every value in Rust has a specific data type that determines its size, the range of values it can hold, and its performance characteristics. This tells the compiler exactly how to work with that data.

There are two broad categories of data types in Rust: scalar and compound.


Data Type Categories


Type Inference

The Rust compiler can usually infer the type of a variable based on its value and how it is used. For example, if you write:

let xyz = 10;

Rust automatically assigns the type i32, because that is the default integer type.

The table below shows the default inferred types for common literals in Rust:

Literal / ValueExampleInferred TypeNotes
Integer10, -5, 0i32Default integer type
Floating-point3.14, 2.0f64Default float type
String literal"hello"&strString slice, not String
Owned stringString::from("hi")StringMust be explicitly created
Booleantrue, falseboolOnly two possible values
Character'a', 'Z'charUnicode scalar value
Array[1, 2, 3][i32; 3]Uses default i32
Tuple(1, 2.0)(i32, f64)Each element inferred separately
OptionSome(5)Option<i32>Depends on the inner value
ResultOk(10)Result<i32, _>Error type often unknown (_)

When You Must Annotate the Type

Sometimes inference is not enough and you must tell Rust the type explicitly. A common example is the parse method, which can convert a string into many different types — so Rust needs you to specify which one:

src/main.rs
fn main() { let number_value: u32 = "12".parse().expect("Not a number!"); println!("{}", number_value); }

Without the : u32 annotation, the compiler produces this error:

error[E0282]: type annotations needed --> src/main.rs:2:9 | 2 | let number_value = "12".parse().expect("Not a number!"); | ^^^^^^^^^^^^ ----- type must be known at this point

The parse method converts a string value into a number. The expect method handles the Result it returns — if parsing succeeds it returns the value 12, otherwise the program panics with the message "Not a number!".


Scalar Types

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

  1. Integer
  2. Floating-point
  3. Boolean
  4. Character

Integer Types

An integer is a whole number with no decimal (fractional) part. In Rust, integer types are prefixed with either i (signed) or u (unsigned):

  • Signed (i) — can hold negative and positive values
  • Unsigned (u) — can hold only zero and positive values
LengthSignedUnsigned
8-biti8u8
16-biti16u16
32-biti32u32
64-biti64u64
128-biti128u128
Architecture-dependentisizeusize

The value range of each type is determined by the number of bits it uses:

LengthSigned RangeUnsigned Range
8-biti8: −128 → 127u8: 0 → 255
16-biti16: −32,768 → 32,767u16: 0 → 65,535
32-biti32: −2,147,483,648 → 2,147,483,647u32: 0 → 4,294,967,295
64-biti64: −9,223,372,036,854,775,808 → 9,223,372,036,854,775,807u64: 0 → 18,446,744,073,709,551,615
128-biti128: −170,141,183,460,469,231,731,687,303,715,884,105,728 → 170,141,183,460,469,231,731,687,303,715,884,105,727u128: 0 → 340,282,366,920,938,463,463,374,607,431,768,211,455
Arch-dependentSame as i32 on 32-bit, i64 on 64-bitSame as u32 on 32-bit, u64 on 64-bit

Each signed variant can store numbers from −(2ⁿ⁻¹) to 2ⁿ⁻¹ − 1, where n is the number of bits. So i8 stores −128 to 127. Each unsigned variant stores 0 to 2ⁿ − 1, so u8 stores 0 to 255.

The isize and usize types depend on the architecture of the machine your program runs on — 64-bit on a 64-bit system and 32-bit on a 32-bit system. They are primarily used for indexing collections.

Integer Literals

You can write integer values in several formats. Rust also allows underscores (_) as visual separators to improve readability — for example 1_000_000 instead of 1000000:

FormatExamples
Decimal1_000, 42, 7_654_321
Hex0x1A, 0xFF, 0xdead_beef
Octal0o12, 0o755, 0o123_456
Binary0b1010, 0b1101_0011, 0b0001_1110
Byte (u8 only)b'A', b'z', b'0', b'\n'

You can also append a type suffix directly to the literal to specify its type — for example, 46u8 means the number 46 stored as an unsigned 8-bit integer.

Integer Overflow

Integer overflow is an important concept to understand in Rust. If you try to assign a value beyond a type’s range — such as adding 1 to a u8 of value 255 — the behaviour depends on how the program is compiled:

  • Debug mode — Rust detects the overflow and panics at runtime, helping you catch mistakes early.
  • Release mode — Rust performs wrapping arithmetic, where the value wraps around to the start of the range. So 255 + 1 becomes 0.
src/main.rs
fn main() { let x: u8 = 255; let y = x + 1; // overflow println!("{}", y); } // Debug mode → program panics // Release mode → output is 0

To handle overflow safely and explicitly, Rust provides dedicated methods:

src/main.rs
fn main() { let x: u8 = 255; let a = x.wrapping_add(1); // 0 — wraps around let b = x.checked_add(1); // None — returns None on overflow let c = x.overflowing_add(1); // (0, true) — value + overflow flag let d = x.saturating_add(1); // 255 — clamps to the maximum println!("{:?} {:?} {:?} {:?}", a, b, c, d); }

Use these methods whenever you need fine-grained control over overflow behaviour. They make your intent explicit and prevent unexpected results.


Floating-Point Types

Rust has two floating-point types: f32 and f64, representing 32-bit and 64-bit precision respectively.

  • f64 is the default because modern CPUs handle 64-bit and 32-bit floats at roughly the same speed, and f64 offers significantly more precision.
  • Rust has signed floats only — there is no unsigned floating-point type.
  • Both types follow the IEEE-754 standard.
src/main.rs
fn main() { let a = 21.8; // f64 — inferred default let b: f32 = 21.9; // f32 — explicitly annotated }

Boolean Type

Like most programming languages, Rust’s Boolean type has exactly two possible values: true and false. A Boolean is one byte in size and is represented by the keyword bool.

src/main.rs
fn main() { let is_active = true; let is_done: bool = false; // with explicit type annotation }

Booleans are most commonly used in conditional expressions such as if / else and while loops.


Character Type

The char type is Rust’s most primitive way to represent a single Unicode character. Unlike strings, a char always holds exactly one character — whether that is a letter, digit, symbol, or emoji.

// char uses single quotes; &str / String uses double quotes let c: char = 'A'; let s: &str = "Hello";

Size and Unicode Support

Rust’s char is 4 bytes (32 bits) in size — large enough to hold any Unicode scalar value. This means a char can represent far more than just ASCII:

  • ASCII letters: 'A', 'z'
  • Accented letters: 'é', 'ñ'
  • CJK characters: '你', '日'
  • Emojis: '💖', '🌍'
  • Zero-width spaces and special symbols

Valid Unicode scalar values in Rust range from U+0000 to U+D7FF and U+E000 to U+10FFFF. The range U+D800 to U+DFFF is excluded — those are surrogate pairs, which are only valid within UTF-16 and cannot stand alone as characters.

A char is not the same as a single byte. Because char is always 4 bytes, a string of 5 characters takes 5 char values — but may take fewer bytes when encoded as UTF-8.


Compound Types

Compound types can group multiple values into a single type. Rust has two built-in primitive compound types: tuples and arrays.


Tuple Type

A tuple is a fixed-size collection of values that can have different types. Once declared, a tuple cannot grow or shrink.

src/main.rs
fn main() { let tuple_value: (i32, f64, u8) = (109, 5.8, 12); }

Tuples are written as a comma-separated list of values inside parentheses. Each position in the tuple has its own type — which is why a tuple can mix i32, f64, and u8 in a single value.

Destructuring a Tuple

The most common way to read values out of a tuple is destructuring:

src/main.rs
fn main() { let tuple_value = (109, 5.8, 12); let (x, y, z) = tuple_value; println!("The value of y is: {y}"); }

The let (x, y, z) = tuple_value line breaks the tuple apart into three separate variables. You can now use x, y, and z individually anywhere in the code.

Accessing Tuple Elements by Index

You can also access individual elements using dot notation followed by the zero-based index:

src/main.rs
fn main() { let x: (i32, f64, u8) = (109, 5.8, 12); let first = x.0; // 109 let second = x.1; // 5.8 let third = x.2; // 12 }

The Unit Type

A tuple with no values — written () — is called the unit type. It acts as a placeholder or as the implicit return type of functions that return nothing.

src/main.rs
fn main() { let empty = (); println!("This is the unit value: {:?}", empty); }

Array Type

An array is another way to store a collection of multiple values. Unlike a tuple, every element in an array must have the same type, and arrays in Rust always have a fixed length.

src/main.rs
fn main() { let arr = [11, 22, 33, 44, 55]; }

Stack vs Heap

Arrays are allocated on the stack — making them very fast. This contrasts with vectors, which are stored on the heap and can grow or shrink dynamically.

ArrayVector
SizeFixed at compile timeGrows/shrinks at runtime
StorageStackHeap
SpeedVery fastSlightly slower
Use whenSize is known and constantSize may change

If you are unsure whether to use an array or a vector, choose a vector. Arrays are best when the number of elements is fixed and known at compile time — for example, the days of the week.

src/main.rs
fn main() { // A vector — can grow dynamically let mut vec = vec![1, 2, 3]; vec.push(4); println!("{}", vec[3]); // 4 }

Type Annotation for Arrays

You can annotate an array’s type using square brackets containing the element type and the length, separated by a semicolon:

let a: [f64; 5] = [12.4, 2.4, 3.6, 34.9, 1.45]; // ^^^ ^ // type length

Repeat Initialisation

To create an array where every element has the same value, use a shorthand with an initial value, a semicolon, and the desired length:

src/main.rs
fn main() { let a = [1; 4]; // [1, 1, 1, 1] println!("{:?}", a); }

Accessing and Updating Array Elements

Array elements are accessed using zero-based index notation. If the array is declared as mut, its elements can also be updated:

src/main.rs
fn main() { let mut arr = [14, 12, 33, 44]; let first = arr[0]; let second = arr[1]; println!("Before update: first = {}, second = {}", first, second); arr[0] = 100; arr[1] = 200; println!("After update: first = {}, second = {}", arr[0], arr[1]); }

Runtime Index Checking and Memory Safety

Rust checks array indices at runtime. If you access an index that is out of bounds, the program panics immediately rather than reading from arbitrary memory — a key part of Rust’s memory safety guarantee.

Here is an example that reads an index from the command line:

src/main.rs
use std::io; fn main() { let a = [13, 42, 33, 34, 25]; println!("Please enter an array index."); let mut index = String::new(); io::stdin() .read_line(&mut index) .expect("Failed to read line"); let index: usize = index .trim() .parse() .expect("Index entered was not a number"); let element = a[index]; println!("The value of the element at index {index} is: {element}"); }

If you enter an index that does not exist (e.g., 7 for a 5-element array), Rust panics with a clear error message:

thread 'main' panicked at src/main.rs:19:19: index out of bounds: the len is 5 but the index is 7 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

In languages like C and C++, accessing an out-of-bounds index can silently corrupt memory or crash the program in unpredictable ways. Rust prevents this by stopping the program immediately, before any unsafe memory access can occur.


Summary

TypeCategoryKey Characteristics
i8i128, isizeScalar / IntegerSigned whole numbers
u8u128, usizeScalar / IntegerUnsigned whole numbers
f32, f64Scalar / FloatDecimal numbers (IEEE-754)
boolScalartrue or false, 1 byte
charScalarSingle Unicode character, 4 bytes
(T1, T2, ...)Compound / TupleFixed-size, mixed types
[T; N]Compound / ArrayFixed-size, single type, stack-allocated

What’s Next?

Now that you understand Rust’s type system, you are ready to explore Functions — where you will learn how to define, call, and pass data between functions in Rust.

Last updated on