Skip to content
AgileLogics
6 min read

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 u8 that holds 255 in a debug build stops the program with attempt to add with overflow. Pick a type large enough for your values — when unsure, i32 or i64 for integers and f64 for 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.2 prints 0.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}");
}