4.3isize, usize, and integer literals

Last updated June 11, 2026

Two integer types didn't fit last lesson's tidy table, because their size isn't in their name: it's in your computer.

The pointer-sized pair

usize (unsigned) and isize (signed) match the width of a memory address on the machine the program is compiled for: 8 bytes on the 64-bit hardware you almost certainly own, 4 on older 32-bit targets. Proof by asking:

fn main() {
    println!("usize here: {} bytes", size_of::<usize>());
}
usize here: 8 bytes

Why would a language with ten perfectly good fixed-size integers add two stretchy ones? Because some numbers are about memory itself: how many elements a collection holds, which position you're reading. A memory-sized number deserves a memory-sized type, whatever the machine. That's usize's entire career, and you've met one already: size_of returns a usize.

You won't choose usize often; it arrives on its own. When chapter 18's collections appear, positions and lengths will be usize by decree, and the no-implicit-conversions rule from lesson 4.2 will occasionally make you convert into it deliberately. For ordinary arithmetic, the advice stands: i32 by default, usize when you're talking about memory, isize nearly never (it exists for some rare pointer math; file it under trivia for now).

Integer literals, the complete kit

You know integer literals default to i32 and tolerate underscores (1_000_000, lesson 1.10). Here's the rest of the syntax, all of it occasionally useful and all of it quiz bait.

A literal can carry its type as a suffix, fused right onto the number:

let small = 42u8;        // same as: let small: u8 = 42;
let big = 42u64;         // same as: let big: u64 = 42;

Suffix and annotation mean the same thing; the suffix is handy when there's no let to annotate, like an argument in a function call. (With neither, you get i32.)

Literals can also be written in other bases: prefix 0x for hexadecimal, 0o for octal, 0b for binary. The value is identical regardless of spelling; only the notation changes, and you pick whichever matches how you're thinking about the number:

fn main() {
    println!("{}", 255);
    println!("{}", 0xFF);
    println!("{}", 0o377);
    println!("{}", 0b1111_1111);
}
255
255
255
255

Four spellings, one value. Binary spelling plus underscores is the classic combination for bit-pattern work (chapter 6's optional bitwise lesson), and hex is the native tongue of anything byte-flavored. There's even a character form, b'A', meaning "the u8 whose value is this character's code" (65 here); it'll make more sense after lesson 4.8.

One more piece of compile-time safety rounds out the kit. A literal that doesn't fit its type is rejected outright:

fn main() {
    let x: u8 = 300;
    println!("{x}");
}
error: literal out of range for `u8`
 --> src/main.rs:2:17
  |
2 |     let x: u8 = 300;
  |                 ^^^
  |
  = note: the literal `300` does not fit into the type `u8` whose range is `0..=255`
  = note: `#[deny(overflowing_literals)]` on by default

The compiler quotes you the type's range in the rejection. What happens when a value strays out of range at runtime, where no compiler can pre-check it, is the next lesson, and it's a better story than most languages can tell.

Quiz time

Question #1

What value does each print? 0b1010, 0x10, 1_0_0

Show solution

10 (binary: 8+2), 16 (hex: one sixteen, zero ones), and 100 (underscores are ignored wherever they fall, even in silly places).

Question #2

What's the type of each variable?

let a = 7;
let b = 7u16;
let c: i64 = 7;
let d = size_of::<bool>();
Show solution

a is i32 (the default), b is u16 (suffix), c is i64 (annotation), and d is usize (whatever size_of returns, that's its type; no conversion happened because none was needed).

Question #3

A teammate writes let flags: u8 = 0b1_0000_0000; and insists binary literals are always fine. Predict the compiler's verdict.

Show solution

Rejected: that's binary for 256, one past u8's ceiling, and the literal-out-of-range error will say so, range quoted. The base never matters; the value always does.

Next: what happens when arithmetic itself, not a literal, produces a number too big for its type. Rust's answer involves a panic, a wrap, and a menu.