KAIROS CODERS

Rust Data Types: Integers, Floats, Booleans, Characters, Tuples and Arrays

user

Rahul

August 26, 2026 at 04:59 PM

View Count: 10

Rust Data Types: Integers, Floats, Booleans, Characters, Tuples and Arrays

In the previous article, we learned about variables, mutability, shadowing, constants, and scope.

Now we're going deeper into one of Rust's most important foundations:

The Rust type system.

Every value in Rust has a type.

Rust uses this information to catch mistakes before your program runs.

In this article, you'll learn:

  • What data types are
  • Scalar vs compound types
  • Integer types
  • Signed vs unsigned integers
  • Floating-point types
  • Boolean values
  • Characters
  • Tuples
  • Arrays
  • Type inference
  • Type annotations
  • Type casting
  • Integer overflow
  • Practical examples
  • Common beginner mistakes

What Is a Data Type?

A data type tells Rust what kind of value you're working with.

For example:

 

let age = 30;

 

age contains an integer.

Another example:

 

let price = 99.99;

 

price contains a floating-point number.

And:

 

let active = true;

 

active contains a Boolean.

Conceptually:

age       → integer
price     → floating-point number
active    → Boolean

 

Types allow Rust to determine:

  • How much memory a value needs
  • What operations are valid
  • How the value should be interpreted
  • How the compiler should generate machine code

Rust's Two Main Categories of Types

Rust's built-in types can broadly be introduced as:

Scalar Types
    ↓
One value

Compound Types
    ↓
Multiple values

 

The four primary scalar types are:

  • Integers
  • Floating-point numbers
  • Booleans
  • Characters

The two primitive compound types are:

  • Tuples
  • Arrays

Let's explore each one.


Scalar Types

A scalar represents a single value.

For example:

 

let age: i32 = 30;

 

The variable contains one integer value.

Similarly:

 

let active: bool = true;

 

contains one Boolean value.


Integer Types

Integers represent whole numbers.

Examples:

-10
0
25
1000

 

Rust provides several integer types.

Signed Integers

Signed integers can represent both positive and negative values.

i8
i16
i32
i64
i128
isize

 

For example:

 

let temperature: i32 = -10;

 


Unsigned Integers

Unsigned integers can represent only zero and positive values.

u8
u16
u32
u64
u128
usize

 

For example:

 

let age: u32 = 30;

 

Since an age normally isn't negative, an unsigned integer can sometimes make sense.


Signed vs Unsigned

The difference is straightforward.

Signed

 

let temperature: i32 = -20;

 

Can represent:

negative
zero
positive

 

Unsigned

 

let age: u32 = 30;

 

Can represent:

zero
positive

 

but not:

negative

 


Integer Sizes

The number after i or u represents the number of bits.

For example:

i8

 

uses 8 bits.

i32

 

uses 32 bits.

i64

 

uses 64 bits.

i128

 

uses 128 bits.

Generally:

More bits
   ↓
Larger range of values
   ↓
More memory

 


Integer Ranges

For a signed integer with n bits, the range is approximately:

-2^(n-1) to 2^(n-1)-1

 

For example, i8 can represent:

-128 to 127

 

An unsigned u8 can represent:

0 to 255

 

This makes u8 useful for values such as:

  • RGB color components
  • Raw bytes
  • Binary data
  • Network packets

Why Does Rust Have So Many Integer Types?

Different applications have different requirements.

For example, a small value might use:

 

let age: u8 = 30;

 

A database identifier might use:

 

let user_id: u64 = 123456789;

 

A collection index commonly uses:

 

let index: usize = 10;

 

Choosing an appropriate type can matter when working with:

  • Large datasets
  • Embedded systems
  • Network protocols
  • Binary formats
  • Performance-sensitive applications

As a beginner, i32 is often a convenient default for general integer calculations.


The Default Integer Type

When Rust sees:

 

let number = 10;

 

and there isn't enough context to determine another type, the compiler generally defaults the integer to:

i32

 

So this:

 

let number = 10;

 

is commonly equivalent to:

 

let number: i32 = 10;

 


Explicit Integer Types

You can always specify the type:

 

let small_number: i8 = 100;
let normal_number: i32 = 100000;
let large_number: i64 = 10000000000;
let id: u64 = 123456789;

 

This is especially useful when the type matters to your application's design.


Integer Literals

Rust allows different representations of integer literals.

For example:

 

let decimal = 100;
let hex = 0xff;
let octal = 0o77;
let binary = 0b1010;

 

You can also use underscores to make large numbers easier to read:

 

let population = 1_400_000_000;

 

The underscores don't change the value.

They simply improve readability.


Floating-Point Types

Rust provides two floating-point types:

f32
f64

 

Example:

 

let price: f64 = 999.99;

 

And:

 

let temperature: f32 = 36.5;

 


f32 vs f64

The difference is precision and memory usage.

f32 → 32-bit floating point
f64 → 64-bit floating point

 

Rust commonly defaults floating-point literals to:

f64

 

So:

 

let price = 99.99;

 

is generally inferred as f64.

For many general-purpose applications, f64 is the usual choice when floating-point arithmetic is appropriate.


Floating-Point Arithmetic

You can perform normal arithmetic:

 

fn main() {
    let price = 100.0;
    let quantity = 3.0;

    let total = price * quantity;

    println!("Total: {total}");
}

 

Output:

Total: 300

 

However, floating-point numbers have precision limitations.

For financial systems where exact decimal arithmetic matters, you should carefully choose a representation rather than blindly using f64.


Boolean Type

Rust has a Boolean type:

bool

 

It has exactly two possible values:

 

true false

 

Example:

 

let is_logged_in = true;
let is_admin = false;

 

Booleans are commonly used with conditional logic.


Boolean Operations

You can combine Boolean values using logical operators.

AND

 

&&

 

Example:

 

let logged_in = true;
let admin = true;

let allowed = logged_in && admin;

 

Both conditions must be true.


OR

 

||

 

Example:

 

let is_admin = false;
let is_manager = true;

let can_manage = is_admin || is_manager;

 

At least one condition must be true.


NOT

 

!

 

Example:

 

let active = true;
let inactive = !active;

 

Now:

inactive → false

 


Boolean Example

 

fn main() {
    let age = 25;
    let has_ticket = true;

    let can_enter = age >= 18 && has_ticket;

    println!("Can enter: {can_enter}");
}

 

Output:

Can enter: true

 

Notice that the comparison:

 

age >= 18

 

produces a Boolean.


Character Type

Rust has a char type for a single Unicode scalar value.

Example:

 

let letter: char = 'R';

 

Characters use single quotes.

 

'R'

 

is a character.


Character vs String

This is important:

 

'R'

 

is a char.

But:

 

"R"

 

is a string slice.

They are different types.

Similarly:

 

let letter = 'A';
let word = "Rust";

 

The first contains one character.

The second contains text.


Rust Characters Support Unicode

Rust's char type is not limited to English characters.

For example:

 

let symbol = '₹';
let emoji = '🦀';
let hindi = 'क';

 

These are all valid Rust characters.

This makes Rust's character type useful for Unicode text processing.


Character Size

A Rust char occupies:

4 bytes

 

This is because it represents a Unicode scalar value rather than simply one byte.

This is another important distinction between Rust's char and raw byte data.


Compound Types

Now we move to compound types.

Compound types can group multiple values together.

Rust's two primitive compound types are:

  • Tuples
  • Arrays

Tuples

A tuple groups multiple values into a single value.

Example:

 

let person = ("Rahul", 30, true);

 

The tuple contains:

String slice
Integer
Boolean

 

Tuples can contain different types.

That's an important property.


Accessing Tuple Values

You can access tuple elements using their position.

 

fn main() {
    let person = ("Rahul", 30, true);

    println!("{}", person.0);
    println!("{}", person.1);
    println!("{}", person.2);
}

 

Output:

Rahul
30
true

 

Tuple indexes start at:

0

 

So:

person.0 → first element
person.1 → second element
person.2 → third element

 


Tuple Destructuring

You can also destructure a tuple:

 

fn main() {
    let person = ("Rahul", 30, true);

    let (name, age, active) = person;

    println!("{name}");
    println!("{age}");
    println!("{active}");
}

 

This extracts the individual values into separate variables.

We'll use destructuring frequently later with pattern matching.


Unit Tuple

Rust has a special tuple called the unit type.

It looks like:

 

()

 

A function that doesn't return a meaningful value effectively returns the unit type.

For example:

 

fn greet() {
    println!("Hello!");
}

 

Conceptually, this function returns:

()

 

You'll encounter this idea more often as you learn advanced Rust.


Arrays

An array stores multiple values of the same type.

Example:

 

let numbers = [10, 20, 30, 40, 50];

 

All elements are integers.

Unlike a tuple:

 

let person = ("Rahul", 30, true);

 

an array requires all elements to have the same type.


Array Length Is Fixed

Rust arrays have a fixed length.

For example:

 

let numbers = [10, 20, 30, 40, 50];

 

contains exactly five elements.

Its type can be written as:

[i32; 5]

 

Meaning:

array of 5 i32 values

 


Explicit Array Type

You can specify the type:

 

let numbers: [i32; 5] = [10, 20, 30, 40, 50];

 

The structure is:

[Type; Length]

 

For example:

 

let scores: [u32; 3] = [90, 85, 95];

 


Accessing Array Elements

Use an index:

 

fn main() {
    let numbers = [10, 20, 30, 40, 50];

    println!("{}", numbers[0]);
    println!("{}", numbers[2]);
}

 

Output:

10
30

 

Remember:

first element → index 0
second element → index 1
third element → index 2

 


Array Indexing

For:

 

let numbers = [10, 20, 30, 40, 50];

 

the indexes are:

0 → 10
1 → 20
2 → 30
3 → 40
4 → 50

 

There is no index 5.

Trying to access it is an error.

Rust's runtime safety checks help prevent invalid memory access.


Repeating Values in an Array

Rust provides a convenient syntax for creating arrays containing repeated values.

 

let zeros = [0; 5];

 

This creates:

[0, 0, 0, 0, 0]

 

The syntax:

[value; length]

 

is very useful.

For example:

 

let scores = [100; 10];

 

creates an array containing ten 100 values.


Arrays vs Tuples

This distinction is important.

Tuple

 

let person = ("Rahul", 30, true);

 

Different types are allowed.

Array

 

let scores = [90, 80, 95];

 

All elements must have the same type.

Think:

Tuple
↓
Different kinds of information

Array
↓
Collection of same-type values

 


Arrays vs Vectors

You will eventually encounter another important collection:

 

Vec<T>

 

or simply:

 

Vec

 

A vector is dynamically sized.

For example:

 

let numbers = vec![10, 20, 30];

 

Unlike an array, a vector can grow and shrink.

Conceptually:

Array
→ fixed size

Vector
→ dynamic size

 

We'll dedicate a separate article to vectors and collections.


Type Inference With Arrays

Rust can infer the array type:

 

let numbers = [1, 2, 3, 4, 5];

 

The compiler knows that:

numbers

 

is an array of integers with length 5.

You don't have to write:

 

let numbers: [i32; 5] = [1, 2, 3, 4, 5];

 

unless you need the explicit annotation.


Type Casting

Sometimes you need to convert one numeric type into another.

Rust does not silently perform many numeric conversions.

For example:

 

let age: i32 = 30;
let age_large = age as i64;

 

The as keyword performs an explicit cast.


Example of Numeric Casting

 

fn main() {
    let number: i32 = 100;

    let large_number: i64 = number as i64;

    println!("{large_number}");
}

 

Output:

100

 

The conversion is explicit.

This is intentional.

Rust avoids many implicit conversions that could hide bugs.


Converting Integers to Floating Point

You can also use as:

 

let number: i32 = 10;

let decimal = number as f64;

 

Now:

number → i32
decimal → f64

 


Numeric Types Don't Automatically Mix

Consider:

 

let a: i32 = 10;
let b: i64 = 20;

let result = a + b;

 

This won't compile because Rust does not automatically treat i32 and i64 as interchangeable.

You need to convert one:

 

let result = a as i64 + b;

 

This explicitness prevents accidental type conversions.


Integer Overflow

What happens if an integer exceeds its allowed range?

For example, u8 can represent:

0 → 255

 

So this value is too large:

 

let number: u8 = 256;

 

The compiler will reject an out-of-range literal.

Overflow can also occur during calculations.

Rust's behavior depends on the compilation mode and the operation being performed; in debug builds, arithmetic overflow is checked and can cause a panic, while optimized builds have different overflow behavior unless you use explicit checked/wrapping/saturating operations.

This is an important reason not to ignore numeric types.


Safe Integer Operations

Rust provides methods for explicitly handling overflow.

For example:

 

let value: u8 = 255;

let result = value.checked_add(1);

 

The result is:

None

 

rather than silently producing an unexpected value.

Other useful operations include:

checked_add
checked_sub
checked_mul
saturating_add
saturating_sub
wrapping_add
wrapping_sub

 

We'll explore these in an advanced article about Rust's numeric system.


A Practical Example: Student Scores

Let's combine arrays and integers.

 

fn main() {
    let scores = [85, 92, 78, 95, 88];

    println!("First score: {}", scores[0]);
    println!("Third score: {}", scores[2]);
    println!("Last score: {}", scores[4]);
}

 

Output:

First score: 85
Third score: 78
Last score: 88

 


A Practical Example: User Information

Tuples are useful when grouping a small number of related values.

 

fn main() {
    let user = ("Rahul", 30, true);

    let (name, age, active) = user;

    println!("Name: {name}");
    println!("Age: {age}");
    println!("Active: {active}");
}

 

For larger applications, however, a struct is usually more expressive.

We'll learn structs later.


A Practical Example: RGB Colors

An RGB color contains three components:

Red
Green
Blue

 

An array can represent it:

 

let color: [u8; 3] = [255, 128, 0];

 

Because each component ranges from 0 to 255, u8 is a natural choice.

You could interpret this as:

Red   → 255
Green → 128
Blue  → 0

 


A Practical Example: Weekly Temperatures

An array can store temperatures for seven days:

 

fn main() {
    let temperatures: [i32; 7] = [
        30,
        32,
        31,
        29,
        33,
        34,
        30,
    ];

    println!("Monday: {}", temperatures[0]);
    println!("Sunday: {}", temperatures[6]);
}

 

This demonstrates why fixed-size arrays can be useful.


A Practical Example: Product Data

A tuple can temporarily group product information:

 

fn main() {
    let product = ("Laptop", 75000.0, true);

    println!("Name: {}", product.0);
    println!("Price: {}", product.1);
    println!("Available: {}", product.2);
}

 

Later, you'll learn why a struct is generally better for this kind of domain model.


Type Inference vs Explicit Types

Consider:

 

let age = 30;

 

This is concise.

While:

 

let age: i32 = 30;

 

is explicit.

Neither is automatically "better."

A good rule is:

Let Rust infer obvious types, but explicitly annotate types when they communicate important intent or resolve ambiguity.


Common Beginner Mistakes

Mistake 1: Mixing Integer Types

This won't work:

 

let a: i32 = 10;
let b: i64 = 20;

let result = a + b;

 

Convert explicitly.


Mistake 2: Confusing char and Strings

This:

 

let letter = 'A';

 

is a character.

This:

 

let letter = "A";

 

is a string slice.

They are different types.


Mistake 3: Accessing an Invalid Array Index

Given:

 

let numbers = [1, 2, 3];

 

this is invalid:

 

println!("{}", numbers[3]);

 

Valid indexes are:

0
1
2

 


Mistake 4: Assuming Arrays Can Grow

This:

 

let numbers = [1, 2, 3];

 

has a fixed size.

If you need a dynamically growing collection, you'll generally use a vector:

 

let numbers = vec![1, 2, 3];

 


Mistake 5: Assuming Rust Automatically Converts Numbers

Rust generally requires explicit numeric conversions.

Don't expect:

i32 → i64

 

or:

i32 → f64

 

to happen automatically.

Use explicit conversion when appropriate.


Complete Example

Let's combine several types:

 

fn main() {
    let age: u8 = 30;
    let height: f64 = 5.9;
    let active: bool = true;
    let initial: char = 'R';

    let skills = ["Rust", "Python", "JavaScript"];

    let user = ("Rahul", age, active);

    println!("Initial: {initial}");
    println!("Age: {}", user.1);
    println!("Height: {height}");
    println!("Active: {}", user.2);

    println!("Skill 1: {}", skills[0]);
}

 

This program uses:

  • u8
  • f64
  • bool
  • char
  • Array
  • Tuple
  • String slices

You're now starting to see how Rust's type system fits together.


The Rust Type System at a Glance

You can remember the basic structure like this:

Rust Types
│
├── Scalar
│   ├── Integer
│   │   ├── i8
│   │   ├── i16
│   │   ├── i32
│   │   ├── i64
│   │   ├── i128
│   │   ├── isize
│   │   ├── u8
│   │   ├── u16
│   │   ├── u32
│   │   ├── u64
│   │   ├── u128
│   │   └── usize
│   │
│   ├── Floating Point
│   │   ├── f32
│   │   └── f64
│   │
│   ├── bool
│   └── char
│
└── Compound
    ├── Tuple
    └── Array

 

This is one of the diagrams worth remembering as you continue learning Rust.


Practice Exercises

Exercise 1 — Integer Types

Create variables using:

i8
i32
i64
u8
u32
u64

 

Print each one.


Exercise 2 — Temperature

Create:

 

let celsius: f64 = 25.0;

 

Convert it to Fahrenheit.


Exercise 3 — Tuple

Create a tuple containing:

Name
Age
Country

 

Destructure it and print each value.


Exercise 4 — Array

Create an array containing the marks of five students.

Print:

  • First mark
  • Third mark
  • Last mark

Exercise 5 — RGB

Create:

 

let color: [u8; 3] = [255, 100, 50];

 

Print the RGB values.


Exercise 6 — Type Conversion

Create:

 

let number: i32 = 100;

 

Convert it into:

i64
f64

 

and print both results.


What You Learned

In this article, you learned:

  • What data types are
  • Scalar types
  • Compound types
  • Signed integers
  • Unsigned integers
  • Integer sizes
  • Integer literals
  • Floating-point numbers
  • f32
  • f64
  • Booleans
  • Logical operators
  • Characters
  • Unicode characters
  • Tuples
  • Tuple destructuring
  • Arrays
  • Array indexing
  • Repeated arrays
  • Type inference
  • Type annotations
  • Numeric casting
  • Integer overflow
  • Arrays vs vectors

You now have a strong foundation in Rust's primitive type system.

Pixels to Perfection Design that Impresses

Want to partner with us? let's innovate together