Academy
Integer Types and Literals: Choosing the Right Size for the Right Job
Learn how Rust's fixed-size integer types eliminate platform-dependent bugs, handle overflow safely, and simplify choosing the right size for any system.
8 menit baca
Di halaman ini
- The Complete Integer Type Family
- Integer Literals: Decimal, Hex, Octal, Binary
- Type Inference and Suffixes: How the Compiler Decides
- Overflow: Defined, Not Undefined
- Choosing the Right Integer Type
- Broader Implications: Integers as Architecture Decisions
- Practical Applications & Strategic Takeaways
- Our Commitment to Open Knowledge
- Support Our Mission & Get Your Handbook
Here's a question that seems trivial until you realize how many bugs it's caused throughout computing history: how many bits does your integer need? In C, the answer is "it depends on the compiler and platform." int is "at least 16 bits." long is "at least as big as int." long long is "at least 64 bits." These "at least" qualifiers mean that int might be 16 bits on one platform, 32 on another, and theoretically 64 on a third. Code that works perfectly on your 32-bit embedded system might overflow silently on a 16-bit target. Code that assumes sizeof(int) == 4 works great until someone compiles it for a platform where it doesn't.
Rust looked at this forty-year history of platform-dependent integer sizes and said: no. Every integer type has a fixed, specified, platform-independent size. i32 is 32 bits. Always. Everywhere. On x86, on ARM, on RISC-V, on WebAssembly. If you want 64 bits, you write i64. If you want 8 bits, you write i8. The name is the specification. No guessing, no checking, no "well, on this platform..." qualifying.
The only exceptions are isize and usize, which are pointer-sized—32 bits on 32-bit platforms, 64 bits on 64-bit platforms. And even these are explicitly documented as platform-dependent, so you know exactly what you're getting into when you use them.
At RantAI, where our systems deploy across x86 cloud servers and ARM edge devices, fixed-size integers eliminate an entire category of portability bugs. We never discover in production that an integer overflow occurs on one platform but not another. This article, drawn from Chapter 8, Sections 8.2.6 through 8.2.8 of our guide "The Rust Programming Language," covers integer types, literal syntax, and the rules for choosing the right integer for the job.
The Complete Integer Type Family#
// Signed integers: can hold negative values
let tiny: i8 = -128; // -128 to 127
let small: i16 = -32_768; // -32,768 to 32,767
let default_int: i32 = -2_147_483_648; // -2.1 billion to 2.1 billion
let big: i64 = -9_223_372_036_854_775_808; // ±9.2 quintillion
let huge: i128 = 170_141_183_460_469_231_731_687_303_715_884_105_727; // Enormous
// Unsigned integers: zero and positive only
let ubyte: u8 = 255; // 0 to 255
let ushort: u16 = 65_535; // 0 to 65,535
let uint: u32 = 4_294_967_295; // 0 to 4.2 billion
let ulong: u64 = 18_446_744_073_709_551_615; // 0 to 18.4 quintillion
let uhuge: u128 = 340_282_366_920_938_463_463_374_607_431_768_211_455; // Astronomical
// Platform-dependent sizes
let index: usize = 42; // 32 or 64 bits depending on platform
let offset: isize = -10; // Signed version of usize
Notice the underscores in numeric literals (2_147_483_648). These are visual separators—the compiler ignores them entirely, but they make large numbers dramatically more readable. Compare 4294967295 with 4_294_967_295. Same number, but one is readable and the other is a eyesight test.
Integer Literals: Decimal, Hex, Octal, Binary#
fn main() {
// Decimal (base 10) — the default
let decimal = 255;
// Hexadecimal (base 16) — prefix 0x
let hex = 0xFF; // 255
let hex_upper = 0xDEAD_BEEF; // Underscores work here too
// Octal (base 8) — prefix 0o
let octal = 0o377; // 255
// Binary (base 2) — prefix 0b
let binary = 0b1111_1111; // 255
let flags = 0b0000_0101; // Bits 0 and 2 set
// Type suffixes — explicit type annotation on the literal
let explicit_i64 = 42i64; // i64
let explicit_u8 = 255u8; // u8
let explicit_usize = 100usize; // usize
println!("All equal 255: {} {} {} {}", decimal, hex, octal, binary);
}
Hexadecimal (0x) is invaluable for bit patterns, memory addresses, and color codes. Binary (0b) makes bit flags self-documenting. Octal (0o) is occasionally useful for Unix file permissions. Decimal is for everything else—which is to say, most things.
Rust's octal prefix 0o (zero-oh) is worth highlighting: unlike C, where a leading zero alone makes a number octal (so 0777 is octal 777 = decimal 511, not decimal 777), Rust requires the explicit 0o prefix. This eliminates the infamous C bug where int permissions = 0644; is octal but looks decimal, and int port = 0080; is a syntax error because 8 isn't a valid octal digit. In Rust, 644 is always decimal, and 0o644 is explicitly octal. No confusion. No surprises.
Type Inference and Suffixes: How the Compiler Decides#
When you write an integer literal without a suffix, Rust infers the type from context:
let x = 42; // i32 by default (when no context suggests otherwise)
let y: u64 = 42; // u64 because of the type annotation
let z = 42u8; // u8 because of the suffix
fn takes_i64(n: i64) {}
takes_i64(42); // 42 is inferred as i64 because the function expects i64
let arr: [u16; 3] = [1, 2, 3]; // 1, 2, 3 are inferred as u16
The default integer type is i32—a sensible choice that matches the native word size on most modern hardware and covers the vast majority of integer use cases. If you need something different, either annotate the variable or suffix the literal.
Overflow: Defined, Not Undefined#
In C/C++, signed integer overflow is undefined behavior—the compiler can assume it never happens and optimize accordingly, potentially producing code that does truly bizarre things when overflow occurs. In Rust, integer overflow behavior is defined:
fn main() {
// In debug mode: overflow PANICS (crashes with a clear error message)
// let overflow: u8 = 255 + 1; // Would panic in debug mode
// In release mode: overflow WRAPS (like C's unsigned overflow)
// 255u8 + 1 wraps to 0
// Explicit wrapping, checked, and saturating arithmetic
let wrapped = 255u8.wrapping_add(1); // 0 — wraps around
let checked = 255u8.checked_add(1); // None — returns Option
let saturated = 255u8.saturating_add(1); // 255 — stays at max
let overflowed = 255u8.overflowing_add(1); // (0, true) — value + overflow flag
println!("Wrapped: {}", wrapped); // 0
println!("Checked: {:?}", checked); // None
println!("Saturated: {}", saturated); // 255
println!("Overflowed: {:?}", overflowed); // (0, true)
}
This is one of Rust's most practically important design decisions. Debug mode catches overflows as panics, helping you find bugs during development. Release mode wraps for performance. And when you need specific overflow behavior, explicit methods (wrapping_add, checked_add, saturating_add) let you choose exactly what happens—documented in the code, visible in reviews, impossible to get wrong accidentally.
Choosing the Right Integer Type#
Here's the practical guide:
Use i32 as your default integer. It's the natural size for most computations, matches the default literal type, and is efficient on all modern hardware.
Use usize for array indices, collection lengths, and anything related to memory sizes. It's pointer-sized by design, matching the platform's addressing capability.
Use u8 for raw bytes, binary data, and ASCII characters. It's the natural unit of I/O and binary protocols.
Use i64 or u64 when you need larger ranges—timestamps, file sizes, database IDs, financial calculations (in cents).
Use i128 or u128 for cryptographic operations, UUIDs, or any domain where 64 bits isn't enough.
Avoid unsigned types for general arithmetic unless you specifically need the unsigned semantics. The temptation to use u32 because "this value is never negative" often backfires when you need to subtract and the result wraps around to a huge positive number instead of going negative.
Broader Implications: Integers as Architecture Decisions#
At RantAI, integer type choices are architectural decisions. Our database ID types use u64 because IDs are positive and we need the range. Our array indices use usize because the compiler requires it. Our scientific computations use f64 for decimal values and i64 for counters. Each choice is deliberate, documented by the type itself, and consistent across our entire codebase.
Practical Applications & Strategic Takeaways#
For newcomers: Start with i32. Switch to i64 when 2 billion isn't enough. Use usize for indexing. That covers 99% of cases.
For C veterans: Rust's i32 is always 32 bits. There's no platform-dependent int. Enjoy the certainty.
For performance engineers: Integer size affects memory layout and cache performance. A Vec<u8> uses 8x less memory than a Vec<u64> for the same number of elements. Choose the smallest type that fits your data range.
Our Commitment to Open Knowledge#
RantAI is committed to open education. Integer types and literals are covered in Chapter 8, Sections 8.2.6–8.2.8 of our guide, "The Rust Programming Language," freely available online.
Explore these concepts further: https://trpl.rantai.dev
Support Our Mission & Get Your Handbook#
Get the Handbook on Amazon KDP: https://www.amazon.com/dp/B0DHCMD3F2
Get the Handbook on Google Play Books: https://play.google.com/store/books/details?id=INwfEQAAQBAJ
Have you been bitten by C's platform-dependent integer sizes? Or discovered the joy of checked_add catching an overflow that would have been silent in C++? Share your integer stories below!
#RustLang #IntegerTypes #Literals #Overflow #RantAI #LearnRust #TypeSafety #Portability #SystemsProgramming #FixedSize
Artikel berikutnya
The Rust Language Standard: What "Defined Behavior" Actually Means