Rust 5 ๐ฆ Scalar Data Types: Integers, Floats, Booleans, and Chars
Rust’s type system begins with scalars โ types that represent a single value. The language has four primary scalar categories: integers, floating-point numbers, booleans, and characters. These are the atoms from which all larger data structures are built, and understanding their sizes, ranges, and behaviors is essential before working with collections, structs, or enums.
What distinguishes Rust’s scalars from those of other languages is not their existence โ every language has numbers and booleans โ but the precision with which they are defined. Integer types come in explicit bit widths, floating-point types follow the IEEE-754 standard, booleans occupy exactly one byte, and characters represent full Unicode scalar values rather than ASCII bytes. This precision is a deliberate choice: in systems programming, knowing the exact size and representation of your data is not optional.
This chapter covers the integer family (signed, unsigned, and pointer-sized), the two floating-point types and their trade-offs, the boolean type and its operations, and the character type with its Unicode semantics. We will also examine integer overflow โ a behavior that differs between debug and release builds โ and the methods Rust provides for handling it explicitly.
Key point: Rust provides explicit, sized scalar types: integers from 8 to 128 bits plus pointer-sized variants, two IEEE-754 floating-point types, a one-byte boolean, and a four-byte Unicode character type.
Why these scalar types exist
The data representation problem. Systems programming requires knowing exactly how many bytes a value occupies and what range it can represent. A C int is 16 bits on some platforms and 32 on others; a long varies even more widely. Rust eliminates this ambiguity by making the bit width part of the type name. i32 is always 32 bits, u8 is always 8 bits, and isize/usize are explicitly pointer-sized. This makes code portable across architectures without relying on platform-specific assumptions .
The signedness problem. Unsigned integers cannot represent negative numbers but double the positive range of their signed counterparts. Some algorithms โ bit manipulation, array indexing, hash computation โ are naturally unsigned. Others โ temperature differences, financial balances, coordinate offsets โ are naturally signed. Rust provides both and requires you to choose, making the sign explicit in the type rather than implicit in the programmer’s head .
The floating-point precision problem. Floating-point arithmetic is approximate, but different applications need different trade-offs. f32 is half the size of f64 and faster on some embedded platforms, but it offers only about seven decimal digits of precision. f64 offers roughly sixteen and is the default on modern hardware because the performance difference is negligible for most workloads. Rust exposes both and defaults to f64 for the precision .
The Unicode problem. In C, a char is one byte and represents an ASCII character. Any non-ASCII text must be handled as bytes or wide characters with locale-dependent semantics. Rust’s char is four bytes and represents a Unicode Scalar Value โ a single code point from U+0000 to U+10FFFF, excluding surrogate code points. This means accented letters, Greek symbols, Chinese characters, and emoji are all single char values, though the distinction between a “character” and a “Unicode code point” requires care when working with combined graphemes .
The overflow safety problem. Integer overflow โ when a value exceeds the range of its type โ is undefined behavior in C and a silent wrap in many other languages. Rust makes this a defined, checkable behavior. In debug builds, overflow panics. In release builds, it wraps using two’s complement. And for cases where you need explicit control, Rust provides four method families: wrapping_*, checked_*, overflowing_*, and saturating_* .
a. Integer types and literals
An integer is a number without a fractional component. Rust provides six signed types (i8, i16, i32, i64, i128, isize) and six unsigned types (u8, u16, u32, u64, u128, usize). The number indicates bit width; isize and usize are pointer-sized, meaning 64 bits on a 64-bit architecture and 32 bits on a 32-bit one .
let small: i8 = -128;
let large: u64 = 18_446_744_073_709_551_615;
let index: usize = 42;
let default_int = 5; // inferred as i32
Signed integers range from -(2^(n-1)) to 2^(n-1) - 1. So i8 spans -128 to 127. Unsigned integers range from 0 to 2^n - 1; u8 spans 0 to 255. The i128 and u128 types exist for when you need wider ranges, though they are slower on some platforms because the hardware lacks native 128-bit arithmetic .
Integer literals can be written in several forms, all of which allow underscores as visual separators and type suffixes :
let decimal = 98_222;
let hex = 0xff;
let octal = 0o77;
let binary = 0b1111_0000;
let byte = b'A'; // u8 only
When you do not annotate a type, Rust infers i32 by default. This is generally the fastest integer type, even on 64-bit systems, and its range is sufficient for most calculations . The primary exception is when indexing collections, where you need usize because indices cannot be negative and must match the platform’s pointer width.
b. Floating-point types and operations
Rust has two floating-point types: f32 (single precision, 32 bits) and f64 (double precision, 64 bits). Both follow the IEEE-754 standard, which defines how sign, exponent, and mantissa bits are laid out and how operations like rounding and special values (infinity, NaN) behave .
let single: f32 = 3.14159;
let double = 2.71828; // inferred as f64
The default is f64 because on modern CPUs it is roughly the same speed as f32 but offers significantly more precision โ about 15 to 17 significant decimal digits versus 6 to 9 for f32. On 32-bit embedded systems, f32 may be faster because f64 requires software emulation, but for most desktop and server workloads, f64 is the better default .
All floating-point types are signed; there is no unsigned float. Rust supports the standard arithmetic operations: +, -, *, /, and %. Division by zero in floating-point yields infinity or NaN rather than panicking, following IEEE-754 semantics .
let a = 5.0 / 2.0; // 2.5
let b = 5.0 % 2.0; // 1.0
let c = 0.0 / 0.0; // NaN
c. Booleans and characters
The boolean type in Rust is bool, with two possible values: true and false. It occupies exactly one byte . Booleans are the type of all conditional expressions and the result of comparison operators.
let active: bool = true;
let finished = false;
if active && !finished {
println!("Still running");
}
Boolean values support the bitwise operators & (AND), | (OR), and ^ (XOR), as well as the logical operators && and ||, which short-circuit. A bool can be cast to an integer: true as i32 is 1, and false as i32 is 0 .
The char type represents a single Unicode Scalar Value. It is four bytes in size and is written with single quotes, distinguishing it from string literals which use double quotes .
let letter = 'a';
let greek = 'ฮฑ';
let emoji = '๐ฆ';
let chinese = 'ไธญ';
Unicode Scalar Values range from U+0000 to U+D7FF and U+E000 to U+10FFFF. The gap between D7FF and E000 contains surrogate code points, which are not valid char values because they are used only in UTF-16 encoding and cannot represent a standalone character .
A crucial caveat: a char is not always the same as a human-perceived “character.” Unicode combines some graphemes from multiple code points โ an accented character like “รฉ” can be a single code point (U+00E9) or a base “e” followed by a combining accent (U+0065 U+0301). A char holds one code point, not one grapheme cluster. This distinction becomes important when processing text .
Complete Example Session
// ============================================
// PART 1: INTEGER TYPES
// ============================================
// Signed and unsigned integers with explicit widths.
fn main() {
let tiny: i8 = -128;
let small: u8 = 255;
let medium: i32 = -2_000_000_000;
let huge: u64 = 18_446_744_073_709_551_615;
println!("{} {} {} {}", tiny, small, medium, huge);
}
// ============================================
// PART 2: INTEGER LITERALS
// ============================================
// Multiple representations, underscores, suffixes.
fn main() {
let dec = 1_000_000;
let hex = 0xdead_beef;
let oct = 0o755;
let bin = 0b1010_1010;
let byte = b'Z';
println!("{} {} {} {} {}", dec, hex, oct, bin, byte);
}
// ============================================
// PART 3: DEFAULT INTEGER TYPE
// ============================================
// Unannotated integers default to i32.
fn main() {
let x = 42; // i32
println!("{}", x);
}
// ============================================
// PART 4: FLOATING-POINT TYPES
// ============================================
// f32 and f64; default is f64.
fn main() {
let f: f32 = 3.14;
let d = 2.71828; // f64
println!("{} {}", f, d);
}
// ============================================
// PART 5: FLOATING-POINT SPECIAL VALUES
// ============================================
// IEEE-754 semantics for division.
fn main() {
let inf = 1.0 / 0.0;
let nan = 0.0 / 0.0;
println!("{} {}", inf, nan);
}
// ============================================
// PART 6: BOOLEAN TYPE
// ============================================
// bool is one byte; true or false.
fn main() {
let yes: bool = true;
let no = false;
let result = yes && !no;
println!("{}", result);
}
// ============================================
// PART 7: BOOLEAN CASTING
// ============================================
// true as i32 is 1; false as i32 is 0.
fn main() {
let t = true as i32;
let f = false as i32;
println!("{} {}", t, f);
}
// ============================================
// PART 8: CHARACTER TYPE
// ============================================
// char is four bytes, Unicode Scalar Value.
fn main() {
let a = 'A';
let omega = 'ฮฉ';
let crab = '๐ฆ';
println!("{} {} {}", a, omega, crab);
}
// ============================================
// PART 9: INTEGER OVERFLOW IN DEBUG
// ============================================
// Overflow panics in debug builds.
fn main() {
let mut x: u8 = 255;
// x += 1; // panics: attempt to add with overflow
println!("{}", x);
}
// ============================================
// PART 10: EXPLICIT OVERFLOW HANDLING
// ============================================
// wrapping_add wraps; checked_add returns Option.
fn main() {
let x: u8 = 255;
let wrapped = x.wrapping_add(1); // 0
let checked = x.checked_add(1); // None
println!("{:?} {:?}", wrapped, checked);
}
These ten parts cover the four scalar categories, their literal forms, default inference, special floating-point values, boolean casting, Unicode characters, and overflow behavior. The final example shows the wrapping_* and checked_* method families that give explicit control over edge cases.
Quick Reference
Integer Types
| Type | Size | Range (Signed) | Range (Unsigned) |
|---|---|---|---|
i8 / u8 | 8-bit | -128 to 127 | 0 to 255 |
i16 / u16 | 16-bit | -32,768 to 32,767 | 0 to 65,535 |
i32 / u32 | 32-bit | -2,147,483,648 to 2,147,483,647 | 0 to 4,294,967,295 |
i64 / u64 | 64-bit | -9.2e18 to 9.2e18 | 0 to 1.8e19 |
i128 / u128 | 128-bit | -1.7e38 to 1.7e38 | 0 to 3.4e38 |
isize / usize | pointer | platform-dependent | platform-dependent |
Floating-Point Types
| Type | Size | Precision | Default |
|---|---|---|---|
f32 | 32-bit | ~7 decimal digits | No |
f64 | 64-bit | ~16 decimal digits | Yes |
Scalar Type Sizes
| Type | Size |
|---|---|
| Integer | 8โ128 bits |
| Float | 32 or 64 bits |
bool | 1 byte |
char | 4 bytes |
Overflow Methods
| Method | Behavior on Overflow |
|---|---|
wrapping_add | Wraps to minimum/maximum |
checked_add | Returns None |
overflowing_add | Returns (value, bool) |
saturating_add | Clamps to min/max |
Best Practices
โ Do This:
let x: i32 = 42; // Explicit when clarity matters
let index: usize = 0; // Use usize for indexing
let f = 3.14; // Default f64 for precision
let b: bool = true; // bool for conditionals
let c = '๐ฆ'; // char for Unicode
let result = a.checked_add(b); // Explicit overflow handling
let hex = 0xFF; // Hex for bit patterns
โ Don’t Do This:
let x = 42; // โ Ambiguous when type matters
let index: i32 = 0; // โ i32 for indexing
let f: f32 = 3.14; // โ f32 when f64 works
let c = "a"; // โ String, not char
let x: u8 = 255; x += 1; // โ Panics in debug
let val = a.wrapping_add(b); // โ Silent wrap when unexpected
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
| Overflow panic in debug | Adding 1 to max value | Use wrapping_add, checked_add, or wider type |
| Silent wrap in release | Same operation, different build mode | Use explicit overflow methods |
usize/i32 mismatch | Index requires usize | Cast with as usize or annotate |
char from multiple code points | Grapheme is not a code point | Use string slice for graphemes |
| Float comparison fails | NaN never equals anything | Use is_nan() or epsilon comparison |
Precision loss with f32 | 7 digits insufficient | Use f64 for calculations |
b'A' as u8 only | Byte literal type restriction | Use b'A' as char or 'A' |
Real-World Examples
1. Configuration Constant
const MAX_CONNECTIONS: u32 = 1024;
const TIMEOUT_MS: u64 = 30_000;
2. Array Index
let data = [10, 20, 30];
let i: usize = 1;
let value = data[i];
3. Financial Calculation
let price: f64 = 19.99;
let tax_rate = 0.08;
let total = price * (1.0 + tax_rate);
4. Temperature Conversion
fn c_to_f(c: f64) -> f64 {
c * 9.0 / 5.0 + 32.0
}
5. Boolean Flag
let is_valid = input.len() > 0 && input.starts_with('a');
6. Character Classification
let c = '7';
if c.is_ascii_digit() {
println!("digit");
}
7. Bitmask
let flags: u8 = 0b1010_0101;
let mask = 0b0000_1111;
let low = flags & mask;
8. Safe Addition
match a.checked_add(b) {
Some(sum) => println!("{}", sum),
None => println!("overflow"),
}
9. Saturating Counter
let mut count: u8 = 250;
count = count.saturating_add(10); // 255, not wrap
10. Unicode Iteration
for c in "hรฉllo".chars() {
println!("{}", c);
}
Visual
Integer Types and Ranges
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ SIGNED vs UNSIGNED INTEGER RANGES โ
โ โ
โ i8: -128 โโโโโโโโโโโโโโโโโโโโโโโโ 127 โ
โ u8: 0 โโโโโโโโโโโโโโโโโโโโโโโโ 255 โ
โ โ
โ i16: -32768 โโโโโโโโโโโโโโโโโโโโโโ 32767 โ
โ u16: 0 โโโโโโโโโโโโโโโโโโโโโโโโ 65535 โ
โ โ
โ i32: -2.1B โโโโโโโโโโโโโโโโโโโโโโโ 2.1B โ
โ u32: 0 โโโโโโโโโโโโโโโโโโโโโโโโ 4.2B โ
โ โ
โ isize/usize: pointer-sized (64-bit on 64-bit platforms) โ
โ โ
โ Signed: can go negative, half range in each direction โ
โ Unsigned: non-negative only, full range positive โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Float Precision
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ f32 vs f64 PRECISION โ
โ โ
โ f32 (single precision): โ
โ โโโ 32 bits total โ
โ โโโ 1 sign + 8 exponent + 23 mantissa โ
โ โโโ ~7 decimal digits of precision โ
โ โ
โ f64 (double precision): โ
โ โโโ 64 bits total โ
โ โโโ 1 sign + 11 exponent + 52 mantissa โ
โ โโโ ~16 decimal digits of precision โ
โ โ
โ Default: f64 (same speed on modern CPUs, more precision) โ
โ Use f32 only on memory-constrained embedded systems โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Integer Overflow Behavior
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ OVERFLOW: DEBUG vs RELEASE โ
โ โ
โ let x: u8 = 255; โ
โ x + 1 โ
โ โ
โ DEBUG BUILD: โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ panic: attempt to add with overflow โ โ
โ โ Program crashes immediately. โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โ RELEASE BUILD: โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ Result: 0 (wraps around) โ โ
โ โ No panic; silent two's complement wrapping. โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โ EXPLICIT METHODS (same in both builds): โ
โ โโโ wrapping_add โ 0 โ
โ โโโ checked_add โ None โ
โ โโโ overflowing_add โ (0, true) โ
โ โโโ saturating_add โ 255 โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Unicode Scalar Value Range
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ char: UNICODE SCALAR VALUES (4 bytes) โ
โ โ
โ U+0000 โโโโโโโโโโโโโโโโโโโโโโโโโโโโ U+D7FF โ
valid char โ
โ โ
โ U+D800 โโโโโโโโโโโโโโโโโโโโโโโโโโโโ U+DFFF โ surrogates โ
โ โ
โ U+E000 โโโโโโโโโโโโโโโโโโโโโโโโโโโโ U+10FFFF โ
valid char โ
โ โ
โ Examples: โ
โ 'a' = U+0061 โ
โ 'ฮฑ' = U+03B1 โ
โ 'ไธญ' = U+4E2D โ
โ '๐ฆ' = U+1F980 โ
โ โ
โ A char is one code point, not one grapheme. โ
โ "รฉ" can be U+00E9 or U+0065 U+0301 (e + combining accent). โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Summary
| Item | Value |
|---|---|
| Integer types | i8โi128, u8โu128, isize, usize |
| Default integer | i32 |
| Float types | f32 (single), f64 (double) |
| Default float | f64 |
| Boolean type | bool, 1 byte, values true/false |
| Character type | char, 4 bytes, Unicode Scalar Value |
| Integer literals | Decimal, hex (0x), octal (0o), binary (0b), byte (b'A') |
| Overflow (debug) | Panics |
| Overflow (release) | Wraps (two’s complement) |
| Explicit overflow | wrapping_*, checked_*, overflowing_*, saturating_* |
| char range | U+0000โU+D7FF, U+E000โU+10FFFF |
Key takeaways:
- Integer types are explicitly sized.
i32is always 32 bits,u8always 8, andisize/usizematch the pointer width of the target platform . - Signed and unsigned have different ranges. Signed integers use two’s complement and span negative to positive; unsigned start at zero .
f64is the default float. It offers roughly 16 decimal digits of precision at essentially the same speed asf32on modern hardware .boolis one byte and casts to 0 or 1. It is the result type of comparisons and the condition type forifexpressions .charis four bytes and Unicode-aware. It represents a Unicode Scalar Value, not a byte or a grapheme cluster .- Overflow behavior differs by build mode. Debug panics; release wraps silently. Use explicit methods for predictable behavior .
usizeis required for indexing. Collection indices cannot be negative and must match the platform’s pointer size .- Integer literals support underscores and suffixes.
1_000_000u64is valid and readable .
Remember: Rust’s scalar types are the foundation of every value your programs manipulate. The integer types provide explicit widths and signedness, eliminating platform-dependent ambiguity. The two float types trade precision against size, with f64 as the sensible default. Booleans are one byte and drive all conditional logic. Characters are four bytes and Unicode-aware, capable of representing every symbol from every writing system. The overflow rules โ panic in debug, wrap in release โ and the explicit method families for controlling overflow give you the tools to handle edge cases deliberately. When you choose a scalar type, you are choosing a contract: how much memory it uses, what values it can hold, and how it behaves at its limits.
Stop using slow, ad-bloated tool sites! ๐คฎ
๐ Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
โ
Finance (Mortgage, Interest, Inflation)
โ
Tech (Base64, JSON, Dev Suite, IP)
โ
Health (BMI, BMR, TDEE)
โ
Productivity (Timer, Workspace, QR)
โก๏ธ Fast & Private
๐ No data leaves your device
๐ 100% Free
๐ Use it now: https://tools.kandz.me
๐ Bookmark itโyouโll need it later!