Scalar and Compound Types
Rust's basic building-block types — integers, floats, booleans, characters, tuples, and arrays — and how type inference fits in.
2 min read
Rust is statically typed: every value has a type known at compile time. Most of the time you don't have to write it out — the compiler infers it from context — but it's always there, and it's always checked.
Scalar types
A scalar type represents a single value. Rust has four:
Integers come in signed (i8, i16, i32, i64, i128) and unsigned (u8, u16, u32, u64, u128) variants, named after their bit width. i32 is the default if nothing else is inferred, and it's a reasonable default for most values that don't need to be huge.
let age: u8 = 30; // 0 to 255
let population: u64 = 8_000_000_000;Floating-point numbers are f32 and f64, with f64 as the default:
let price = 19.99; // inferred as f64Booleans are bool, true or false, exactly one byte:
let is_active: bool = true;Characters use char, always a single Unicode scalar value, written with single quotes:
let heart = '❤';
let letter = 'z';Note the single quotes — double quotes are for strings, covered later in this course.
Compound types
Compound types group multiple values into one.
A tuple groups values of different types into a fixed-size collection:
fn main() {
let person: (&str, u8, bool) = ("Ada", 30, true);
let (name, age, active) = person; // destructuring
println!("{name} is {age}");
// or access by index
println!("{}", person.0);
}An array holds multiple values of the same type, with a fixed length known at compile time:
fn main() {
let scores: [i32; 3] = [90, 85, 77];
println!("{}", scores[0]);
let zeros = [0; 5]; // [0, 0, 0, 0, 0]
}Arrays live on the stack and can't grow — for a growable list, you'll want Vec<T>, which the collections lesson later in this course covers. Reaching for an index outside an array's bounds is a common source of bugs in other languages; Rust checks array bounds at runtime and panics with a clear error rather than silently reading garbage memory.
Type inference and annotations
You can almost always leave the type off and let the compiler infer it from usage:
let x = 5; // inferred: i32
let y = 5.0; // inferred: f64
let name = "Ada"; // inferred: &strAnnotate explicitly when the inference would be ambiguous, when a function signature requires it, or simply when it makes the code clearer to a reader:
let guess: u32 = "42".parse().expect("not a number");Here, parse() alone doesn't know what type to produce — the : u32 annotation on guess tells it. Getting comfortable reading these annotations (and knowing when the compiler needs one from you) is most of what it takes to read idiomatic Rust fluently.
Test what you just learned
4 quick questions. Get all of them right to unlock the next lesson.
You can take the quiz without an account — logging in just lets your result count toward your progress.