Originally published on php2rust.dev — part of the PHP to Rust course. View this lesson on the original site.
A scalar type holds one atomic value at a time, not a bundle of fields, not a sequence of elements, not a pointer to a heap allocation described by this type alone. Think “one number,” “one true/false,” or “one character scalar,” rather than a struct, tuple, array, String, or Vec.
Rust’s compound types (you’ll see later) combine or group values, e.g. tuples (i32, bool), arrays [i32; 4], structs, enums. Scalars are the simple building blocks those types are made from.
Rust defines four scalar kinds:
- Integers, whole numbers, signed or unsigned, in fixed bit widths (
i32,u8, …). - Floating-point, fractional numbers with a binary float representation (
f32,f64). - Booleans,
trueorfalse(bool). - Characters, a single Unicode scalar value (
char), not a UTF-8 string.
Why it matters in Rust: scalars are usually Copy (cheap to duplicate), have predictable sizes on the stack, and literals have defaults the compiler picks when you don’t annotate (notably i32 for integers and f64 for floats). Choosing the right scalar width (e.g. u8 vs i64) is part of writing efficient, correct low-level code.
PHP doesn’t use the word “scalar” in the type system the same way, but the ideas line up: int, float, and bool behave like Rust’s numeric and boolean scalars. PHP’s string is flexible (any length, bytes/UTF-8 at the engine level); the closest Rust analogue to “one character” is char, while text in general uses str / String. PHP is dynamically typed, a variable’s “scalar kind” can change at runtime, whereas Rust fixes the type at compile time unless you use something like enum or generics to represent alternatives.
Rust scalar types
Integers (i32, u32, etc.)
Rust integers are fixed-width: you choose how many bits the value uses, and whether it is signed (can be negative) or unsigned (zero and positive only).
| Family | Types | Role |
|---|---|---|
| Signed | i8, i16, i32, i64, i128 |
Negative and positive whole numbers |
| Unsigned | u8, u16, u32, u64, u128 |
Only non-negative whole numbers |
| Pointer-sized | isize, usize |
Same size as a pointer; idiomatic for indices and lengths (e.g. slice indexing) |
What’s the difference between i8, i16, i32, i64, and i128?
They are not different “kinds” of integer in the sense of different language rules, they are all signed whole numbers. The digit in the name is only the width in bits (8, 16, 32, 64, 128):
i8→ 8 bits in memory → smallest range (about −128 … 127).i16→ 16 bits → wider range (about ±32k).i32→ 32 bits → default for literals when inferred; range in the billions.i64→ 64 bits → much larger; typical for file sizes, epoch-nanosecond timestamps, etc.i128→ 128 bits → largest standard fixed-width signed type.
So i16 is not “an i8 with extra features”, it’s the same idea (signed int) with more bits, so it can hold larger (and more negative) values and uses more space. You cannot silently treat an i8 as an i32 or vice versa; changing width needs an explicit cast (e.g. i8 → i32 as x as i32) so overflows are visible in the type system.
The u* line works the same way: u8, u16, … differ only by bit width. The u means unsigned (no negative values); u16 is not “the unsigned version of i16” in the sense of identical range, same 16 bits, but unsigned tops out at 65_535 while signed i16 tops out at 32_767.
Exact ranges, overflow, and when to pick each width: Integers.
If you do not write a type and the compiler can infer it, integer literals usually become i32. You can force a literal’s type with a suffix (underscore optional before the type name):
42,-7, typically inferred asi32100_u64or100u64, literal is au64255_u8, fits inu8(0–255)
Underscores in the digits are allowed for readability: 1_000_000, 0xFF_FF (hex), 0b1010_1100 (binary). See Integers for ranges, overflow, and isize/usize in detail.
Floating-point (f32, f64)
Rust’s floats are binary IEEE-754 (like most languages). f64 is the default when you write a float literal without annotation; f32 is single precision.
| Family | Types | Role |
|---|---|---|
| Single precision | f32 |
32 bits (~7 significant decimal digits). Use when you need less memory, GPU/math APIs, or to match C float. Literal suffix: 2.0_f32 / 2.0f32 (need a decimal point so it isn’t an integer). |
| Double precision | f64 |
64 bits (~15–16 significant decimal digits). Default for float literals (3.14, 1.0e-3). Usual choice for general numeric work. |
Literal recap:
3.14,1.0e-3, typically inferred asf642.0_f32, forces anf32literal (2alone is still an integer literal)
Use f32 when you explicitly need less memory or are matching a C API; otherwise f64 is the usual choice. More on precision and pitfalls: Floating-point numbers.
Boolean (bool)
Rust’s bool is its own type, not an alias for 0/1 like in C.
| Family | Types | Role |
|---|---|---|
| Logical | bool |
Only two values: true and false. Used for conditions, if / while, and logical operators &&, ` |
| Representation | 1 byte (layout / ABI) | Small and Copy. You cannot silently use an i32 (or other number) as a bool, conversions are explicit. |
Character (char)
A Rust char is one Unicode scalar value (a code point that is not a surrogate). It is not a string: str / String are UTF-8; char is always 4 bytes in memory (UTF-32–style scalar).
| Family | Types | Role |
|---|---|---|
| Unicode scalar | char |
Holds a single valid scalar: ASCII, accented letters, emoji, etc. Cannot represent a full user-perceived “grapheme” that needs multiple scalars, use str for that. |
| Literals | 'a', 'é', '🦀' |
Single quotes only. Double quotes build a &str, not a char. |
| Escapes | '\n', '\t', '\\', '\'', '\u{1F980}' |
Same expressive power as string escapes; '\u{…}' is hex, 1–6 digits. |
You cannot put more than one scalar inside one char literal, use a str for "ab" or multi-scalar graphemes.
fn main() {
let n: i32 = 42;
let pi: f64 = 3.14159;
let flag: bool = true;
let letter: char = 'R';
println!("n={}, pi={}, flag={}, letter={}", n, pi, flag, letter);
}
Rust requires explicit or inferred types. Default integer type is i32, default float is f64.
PHP "scalar" types
PHP has scalar types: int, float, bool, string. Types are checked at runtime (or at compile time with strict types):
<?php
$n = 42; // int
$pi = 3.14159; // float
$flag = true; // bool
$letter = 'R'; // string (single char is still a string)
echo "n=$n, pi=$pi, flag=$flag, letter=$letter\n";
Comparison
- Rust: Static typing; many integer sizes (i8–i128, u8–u128);
char= 1 Unicode scalar; default int is i32, default float is f64. - PHP: Dynamic typing; single
int; characters are single-char strings.
References
What's next
Next: Integers.
From PHP to Rust — Rust Syntax and Functions