Data Types

A practical tour of integers, floats, booleans, characters, tuples, and arrays — and how Rust quietly figures out most of this for you.

12 min read•4 learning objectives

What You'll Learn

  • Tell the difference between scalar and compound types
  • Pick the right integer and float types without overthinking it
  • Use tuples and arrays, and know when each one fits
  • Understand why Rust asks for type annotations sometimes but not others

Rust is statically typed — every value has a known type, and that type can't change. That might sound restrictive coming from JavaScript or Python, but in practice it mostly stays out of your way: Rust can usually figure out the type from how you use a value, a trick called type inference.

Rust
let score = 100;        // Rust infers this is an i32
let pi = 3.14;          // and this is an f64
let initial = 'A';      // and this is a char

You only have to spell out a type when Rust genuinely can't tell — we'll hit one of those cases in a moment. Let's go through the types you'll actually use.

Integers

An integer is a number with no fractional part. Rust has several integer types, named by how many bits they use and whether they can be negative:

SizeSigned (can be negative)Unsigned (zero or positive)
8-biti8u8
16-biti16u16
32-biti32u32
64-biti64u64
128-biti128u128
pointer-sizedisizeusize

That looks like a lot to memorize. Here's the shortcut: when you're not sure, use i32. It's Rust's default integer type and it's fast on basically every modern machine. You'll reach for the others in specific situations — for example, usize shows up naturally when you're indexing into collections, because that's the type Rust uses to represent "a length or a position in memory."

Rust
let age: u8 = 32;          // small and never negative — u8 fits fine
let temperature: i32 = -10; // can go below zero, so it needs a signed type
let population: u64 = 8_000_000_000; // big number — needs more bits

// underscores are just visual separators; Rust ignores them
println!("{population}");

What about overflow? Each integer type has a maximum value — u8 tops out at 255, for instance. In a debug build, Rust panics (crashes loudly) if your math overflows one of these. In a release build, it quietly wraps around instead. Neither is usually what you want in a real calculation — it's a sign you picked a type that's too small for the job.

Floating-point numbers

For numbers with a decimal point, Rust gives you f32 and f64. Use f64 unless you have a specific reason not to — it's the default, and on modern CPUs it's just as fast as f32 while being noticeably more precise.

Rust
let price = 19.99;       // f64 by default
let discount: f32 = 0.15; // explicitly f32, if you really need it

let total = price * (1.0 - discount as f64);
println!("Total: {total}");

Notice discount as f64 — Rust won't silently convert between numeric types for you, even ones that seem obviously compatible. You have to ask for the conversion explicitly with as. It feels picky at first; it's actually Rust refusing to let a subtle bug sneak past you.

Booleans

Exactly what you'd expect: true or false, written as bool, one byte in size.

Rust
let is_logged_in = true;
let has_permission: bool = false;

if is_logged_in && !has_permission {
    println!("You're in, but some things are locked.");
}

Characters

Rust's char type represents a single Unicode character — written with single quotes, which is how Rust tells a char apart from a string (double quotes). And "Unicode" is doing real work here: a Rust char is four bytes and can represent way more than ASCII letters.

Rust
let letter = 'R';
let emoji = '🦀';
let symbol = 'λ';

println!("{letter} {emoji} {symbol}");

🦀, by the way, is basically Rust's mascot — you'll see "Ferris the crab" all over the community. Now you know why people drop crab emojis in Rust code samples.

Tuples — group different types together

A tuple bundles multiple values, possibly of different types, into a single compound value. Once created, a tuple's length is fixed — you can't grow or shrink it.

Rust
let user: (&str, u32, bool) = ("sora", 29, true);

// destructuring pulls the values out by position
let (name, age, is_admin) = user;
println!("{name} is {age} years old. Admin? {is_admin}");

// or reach into a specific slot with .0, .1, .2
println!("Just the name: {}", user.0);

Tuples are great for quickly grouping a handful of related values — especially for returning more than one thing from a function, which we'll do in the very next lesson.

Arrays — same type, fixed length

An array holds multiple values of the same type, and — like a tuple — its length is fixed once created. Reach for an array when you know exactly how many elements you need and that number won't change, like the months of the year.

Rust
let primes = [2, 3, 5, 7, 11];
let zeros = [0; 5]; // shorthand for [0, 0, 0, 0, 0]

println!("First prime: {}", primes[0]);
println!("How many zeros: {}", zeros.len());

Try changing primes[0] to primes[10] and running it — Rust will panic with an "index out of bounds" error rather than silently handing you garbage memory. That's the safety net working exactly as designed: a class of bugs that plagues C and C++ simply can't happen here.

Most of the time, though, you'll want a list that can grow — and for that, Rust has Vec<T>, which gets its own lesson later in this course.

Quick exercise

Declare a tuple representing a product: a name (&str), a price (f64), and a quantity in stock (u32). Destructure it into three variables and print a sentence describing the product using all three. Bonus: what happens if you try to print product.3?

You now know how Rust represents the basic shapes of data. Next, let's wrap logic around that data with functions.