Scalar Data Types
Prerequisites: Variables, mutability & constants
A scalar type holds a single value. Rust has four: integers, floating-point numbers, booleans, and characters.
Syntax
let a: i32 = -42; // signed integer
let b: u8 = 255; // unsigned integer
let c: f64 = 2.5; // floating-point number
let d: bool = true; // boolean
let e: char = 'R'; // character (single quotes)
Integers come in fixed sizes. Types starting with i are
signed (can be negative); types starting with u are unsigned
(zero or positive):
| Size | Signed | Unsigned |
|---|---|---|
| 8-bit | i8 |
u8 |
| 16-bit | i16 |
u16 |
| 32-bit | i32 |
u32 |
| 64-bit | i64 |
u64 |
| 128-bit | i128 |
u128 |
| pointer | isize |
usize |
If you don't specify a type, integers default to i32. usize
matches your machine's pointer size (64-bit on most computers) and is
the type used for lengths and indexes. Integer literals can use _ as
a separator (1_000_000) and prefixes for other bases: 0xff
(hex), 0o77 (octal), 0b1010 (binary).
Floating-point numbers are f64 (the default) and f32. Both
follow the IEEE-754 standard.
Booleans are bool, with the values true and false.
Characters are char, written in single quotes. A char is a
Unicode scalar value, so it's 4 bytes and can hold letters from any
language or an emoji — not just ASCII.
Integer overflow
Each integer type has a fixed range (u8 holds 0 to 255). If
arithmetic goes outside that range, a debug build panics (stops
with an error) and a release build silently wraps around. When
wrapping or checking is what you want, say so explicitly with methods
such as wrapping_add or checked_add.
Example
fn main() {
let count = 42; // i32 by default
let small: u8 = 255;
let big: i64 = 9_000_000_000;
let hex = 0xff;
let binary = 0b1010;
println!("{count} {small} {big} {hex} {binary}");
let price = 9.99; // f64 by default
let half: f32 = 0.5;
println!("{price} {half}");
let is_ready = true;
let letter = 'R';
let accented = 'é';
println!("{is_ready} {letter} {accented}");
println!("i32 range: {} to {}", i32::MIN, i32::MAX);
println!("u8 max: {}", u8::MAX);
println!("char size: {} bytes", std::mem::size_of::<char>());
println!("255 + 1 wrapping: {}", small.wrapping_add(1));
}
Expected output
42 255 9000000000 255 10
9.99 0.5
true R é
i32 range: -2147483648 to 2147483647
u8 max: 255
char size: 4 bytes
255 + 1 wrapping: 0
Common mistakes
- A literal that doesn't fit its type.
let small: u8 = 256;is caught at compile time:
error: literal out of range for `u8`
- Overflow at runtime. Adding to a
u8that holds255in a debug build stops the program withattempt to add with overflow. Pick a type large enough for your values — when unsure,i32ori64for integers andf64for decimals. - Using double quotes for a
char."R"is a string,'R'is a character; they're different types. - Expecting floats to be exact.
0.1 + 0.2prints0.30000000000000004, just as in most languages.
Practice
Print the largest value a u16 can hold, the smallest value an i8
can hold, and a char variable holding 'Z', each on its own line.
Show solution
fn main() {
let letter = 'Z';
println!("{}", u16::MAX);
println!("{}", i8::MIN);
println!("{letter}");
}