2.5 Data Types and Annotations
Rust is a statically typed language, meaning the type of every variable must be known at compile time. The compiler can often infer the type, but you can also provide explicit type annotations. Once assigned, a variable’s type cannot change.
2.5.1 Primitive Data Types
Rust offers a standard set of primitive types:
- Integers: Signed (
i8
,i16
,i32
,i64
,i128
,isize
) and unsigned (u8
,u16
,u32
,u64
,u128
,usize
). The number indicates the bit width.isize
andusize
are pointer-sized integers (likeptrdiff_t
andsize_t
in C). - Floating-Point:
f32
(single-precision) andf64
(double-precision). - Boolean:
bool
(can betrue
orfalse
). - Character:
char
represents a Unicode scalar value (4 bytes), capable of holding characters like ‘a’, ‘國’, or ‘😂’. This contrasts with C’schar
, which is typically a single byte.
2.5.2 References
In addition to value types like i32
, Rust also supports references—safe, managed pointers that refer to data stored elsewhere in memory. Similar to C pointers, references hold the address of a value, introducing a level of indirection.
Rust references can be either immutable or mutable, allowing temporary access to data without transferring ownership or making a copy. This is especially useful for passing data to functions efficiently.
To create a reference, Rust uses the &
operator for immutable access and &mut
for mutable access. The *
operator can be used to access (dereference) the value behind a reference, although in many cases this happens implicitly.
References are covered in more depth in Chapter 5. Chapter 6 will explore them in full detail, as part of the discussion on Ownership, Borrowing, and Memory Management.
Below is a short example demonstrating how to pass a mutable reference to a function:
fn inc(i: &mut i32) { *i += 1; } fn main() { let mut v = 0; inc(&mut v); println!("{v}"); // 1 let r = &mut v; inc(r); println!("{}", *r); // 2 }
2.5.3 Type Inference
The compiler can often deduce the type based on the assigned value and context.
fn main() { let answer = 42; // Type i32 inferred by default for integers let pi = 3.14159; // Type f64 inferred by default for floats let active = true; // Type bool inferred println!("answer: {}, pi: {}, active: {}", answer, pi, active); }
2.5.4 Explicit Type Annotation
Use a colon :
after the variable name to specify the type explicitly, which is necessary when the compiler needs guidance or you want a non-default type (e.g., f32
instead of f64
).
fn main() { let count: u8 = 10; // Explicitly typed as an 8-bit unsigned integer let temperature: f32 = 21.5; // Explicitly typed as a 32-bit float println!("count: {}, temperature: {}", count, temperature); }
2.5.5 Comparison with C
In C, basic types like int
can have platform-dependent sizes. C99 introduced fixed-width integer types in <stdint.h>
(e.g., int32_t
, uint8_t
), which correspond directly to Rust’s integer types. C lacks built-in type inference like Rust’s.