# 4.2 Integer types > Rust's ten integer types, their exact ranges, signed versus unsigned, and why mixing them is a compile error rather than a bug. Source: https://learnrust.net/chapter-4/integer-types/ An **integer** is a whole number: no fractional part, possibly negative. Rust gives you ten integer types, and despite the apparent abundance, the system fits in one sentence: pick a size (8, 16, 32, 64, or 128 bits) and pick whether negatives are allowed (`i` for signed, `u` for unsigned). | Type | Size | Range | |---|---|---| | `i8` | 1 byte | −128 to 127 | | `i16` | 2 bytes | −32,768 to 32,767 | | `i32` | 4 bytes | −2,147,483,648 to 2,147,483,647 | | `i64` | 8 bytes | −9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 | | `i128` | 16 bytes | about −1.7 × 10³⁸ to 1.7 × 10³⁸ | | `u8` | 1 byte | 0 to 255 | | `u16` | 2 bytes | 0 to 65,535 | | `u32` | 4 bytes | 0 to 4,294,967,295 | | `u64` | 8 bytes | 0 to 18,446,744,073,709,551,615 | | `u128` | 16 bytes | 0 to about 3.4 × 10³⁸ | The ranges follow from lesson [4.1](@/chapter-4/introduction-to-fundamental-data-types.md)'s arithmetic: n bits make 2ⁿ patterns. An unsigned (`u`) type spends all its patterns on 0 upward, so `u8` covers 0 through 255. A **signed** (`i`) type spends half its patterns on negatives, so `i8` covers −128 through 127: same 256 patterns, different neighborhood. Need the exact bounds in code? Every type carries them as constants: ```rust fn main() { println!("i32 spans {} to {}", i32::MIN, i32::MAX); println!("u16 spans {} to {}", u16::MIN, u16::MAX); } ``` ``` i32 spans -2147483648 to 2147483647 u16 spans 0 to 65535 ``` ## Which one? The honest answer is that one of them is the default and the rest are for reasons: {% callout(kind="best", title="Best practice") %} Use `i32` unless you have a specific reason not to. It's Rust's default for integer literals, it's comfortably large for most quantities, and it's fast on every mainstream processor. Reach for `i64`/`u64` when values can exceed two billion in real use (file sizes, timestamps, global counters), `u8` when you're working with raw bytes (chapter 18 will), and the others when a measured reason appears. The 128-bit types are for cryptography-and-similar territory; you'll know. {% end %} What about unsigned for values that "can't be negative," like an age or a count? Tempting, sometimes right, and worth a moment of caution: subtraction is where unsigned types bite. A count of 3 minus a count of 5 is −2, and −2 does not exist in `u32`'s world; exactly what happens then is the next lesson's whole subject ([4.4](@/chapter-4/integer-overflow.md)), but "nothing good by accident" is a fair preview. Prefer signed for quantities you'll do arithmetic on, and treat unsigned as a statement that negative values are *impossible*, not merely unexpected. (One unsigned type is unavoidable and everywhere, `usize`, and it gets the next lesson, [4.3](@/chapter-4/isize-usize-and-integer-literals.md).) ## The classic disaster that doesn't compile here If you ask a C++ veteran about unsigned types, you'll hear war stories, and the worst ones come from *mixing*: compare a signed and an unsigned value there, and the signed one is silently converted, so `-1 < 1u` evaluates to **false** (the −1 becomes 4,294,967,295 on the way in). Programs make absurd decisions, nothing crashes, and someone debugs it on a weekend. Here's that bug, attempted in Rust: ```rust fn main() { let signed: i32 = -1; let unsigned: u32 = 1; if signed < unsigned { println!("sanity prevails"); } } ``` ``` error[E0308]: mismatched types --> src/main.rs:4:17 | 4 | if signed < unsigned { | ------ ^^^^^^^^ expected `i32`, found `u32` | | | expected because this is `i32` ``` Rust has **no implicit numeric conversions**, none, not even the "safe-looking" ones. Different integer types don't compare, don't add, and don't pass for each other without an explicit conversion that you write and therefore think about (lesson [4.10](@/chapter-4/numeric-conversions.md)). The entire silent-mixing genre of bug is not rare in Rust; it's unrepresentable. {% callout(kind="note", title="Key insight") %} This is the chapter 0 sales pitch keeping its shape at small scale: where C++ trusts you with a silent conversion and learncpp.com needs a lesson titled "why to avoid unsigned," Rust converts the trap into a compile error and unsigned types back into ordinary tools. The strictness that nags you ("just compare them!") is the same strictness that deleted the weekend debugging session. You'll be choosing types on their merits, not on their blast radius. {% end %} ## Quiz time **Question #1** Without the table: what's the largest value of `u16`, and how do you derive it from the size?
Show solution 65,535. Sixteen bits make 2¹⁶ = 65,536 patterns; spent on 0 upward, the largest is 2¹⁶ − 1.
**Question #2** Pick a sensible type for each, with one phrase of why: (a) a person's age in years, (b) the byte count of a possibly-huge file, (c) a temperature *change*, which might be negative, (d) one byte read from a network packet.
Show solution (a) `i32`: the default; nothing about ages needs anything else (resist `u8` cleverness; ages get subtracted). (b) `u64`: real files exceed `i32`/`u32` territory, and a negative byte count is meaningless. (c) `i32`: it's signed by nature. (d) `u8`: it *is* a byte. Reasonable people defend other choices; the marks are for "default to `i32`, deviate with a reason."
**Question #3** Predict the compiler's reaction: ```rust fn main() { let total: i64 = 100; let count: i32 = 4; println!("{}", total + count); } ```
Show solution E0308, mismatched types: `i64` and `i32` don't add, even though both are signed integers and the math is obviously harmless. No implicit conversions means *no* implicit conversions; lesson 4.10 shows the explicit fix.
Two integer types remain, the odd ones whose size depends on your machine, and they come bundled with the rest of the literal syntax.