Data Types
Rust is statically typed - all variable types must be known at compile time. The compiler can usually infer types, but explicit annotations are required when multiple types are possible:
let guess: u32 = "42".parse().expect("Not a number!");
Without the type annotation:
$ cargo build
Compiling no_type_annotations v0.1.0 (file:///projects/no_type_annotations)
error[E0284]: type annotations needed
--> src/main.rs:2:9
|
2 | let guess = "42".parse().expect("Not a number!");
| ^^^^^ ----- type must be known at this point
|
= note: cannot satisfy `<_ as FromStr>::Err == _`
help: consider giving `guess` an explicit type
|
2 | let guess: /* Type */ = "42".parse().expect("Not a number!");
| ++++++++++++
For more information about this error, try `rustc --explain E0284`.
error: could not compile `no_type_annotations` (bin "no_type_annotations") due to 1 previous error
Scalar Types
Rust has four primary scalar types: integers, floating-point numbers, booleans, and characters.
Integer Types
Table 3-1: Integer Types in Rust
Length | Signed | Unsigned |
---|---|---|
8-bit | i8 | u8 |
16-bit | i16 | u16 |
32-bit | i32 | u32 |
64-bit | i64 | u64 |
128-bit | i128 | u128 |
arch | isize | usize |
Signed variants store numbers from −(2n − 1) to 2n − 1 − 1, unsigned from 0 to 2n − 1. isize
and usize
depend on target architecture (64-bit on 64-bit systems).
Integer literals:
Table 3-2: Integer Literals in Rust
Number literals | Example |
---|---|
Decimal | 98_222 |
Hex | 0xff |
Octal | 0o77 |
Binary | 0b1111_0000 |
Byte (u8 only) | b'A' |
Default integer type is i32
. Use isize
/usize
for collection indexing.
Integer Overflow
In debug mode, integer overflow causes panics. In release mode (--release
), Rust performs two’s complement wrapping. Handle overflow explicitly with:
wrapping_*
methods (e.g.,wrapping_add
)checked_*
methods (returnOption
)overflowing_*
methods (return value + overflow boolean)saturating_*
methods (clamp to min/max)
Floating-Point Types
Two floating-point types: f32
and f64
(default). Both are signed and follow IEEE-754 standard.
fn main() { let x = 2.0; // f64 let y: f32 = 3.0; // f32 }
Numeric Operations
Standard mathematical operations: +
, -
, *
, /
, %
. Integer division truncates toward zero.
fn main() { // addition let sum = 5 + 10; // subtraction let difference = 95.5 - 4.3; // multiplication let product = 4 * 30; // division let quotient = 56.7 / 32.2; let truncated = -5 / 3; // Results in -1 // remainder let remainder = 43 % 5; }
Boolean Type
bool
type with values true
and false
. One byte in size.
fn main() { let t = true; let f: bool = false; // with explicit type annotation }
Character Type
char
type represents Unicode scalar values (4 bytes). Use single quotes for char
literals, double quotes for strings.
fn main() { let c = 'z'; let z: char = 'ℤ'; // with explicit type annotation let heart_eyed_cat = '😻'; }
Valid Unicode scalar values: U+0000
to U+D7FF
and U+E000
to U+10FFFF
.
Compound Types
Tuple Type
Fixed-length collection of values with heterogeneous types:
fn main() { let tup: (i32, f64, u8) = (500, 6.4, 1); }
Access elements by destructuring:
fn main() { let tup = (500, 6.4, 1); let (x, y, z) = tup; println!("The value of y is: {y}"); }
Or by index:
fn main() { let x: (i32, f64, u8) = (500, 6.4, 1); let five_hundred = x.0; let six_point_four = x.1; let one = x.2; }
Empty tuple ()
is called unit and represents an empty value or return type.
Array Type
Fixed-length collection of homogeneous types, allocated on the stack:
fn main() { let a = [1, 2, 3, 4, 5]; }
Type annotation syntax:
let a: [i32; 5] = [1, 2, 3, 4, 5];
Initialize with repeated values:
let a = [3; 5]; // [3, 3, 3, 3, 3]
Use Vec<T>
for dynamic arrays. Arrays are better when size is known at compile time.
Array Access
fn main() { let a = [1, 2, 3, 4, 5]; let first = a[0]; let second = a[1]; }
Out-of-bounds access causes runtime panic:
use std::io;
fn main() {
let a = [1, 2, 3, 4, 5];
println!("Please enter an array index.");
let mut index = String::new();
io::stdin()
.read_line(&mut index)
.expect("Failed to read line");
let index: usize = index
.trim()
.parse()
.expect("Index entered was not a number");
let element = a[index];
println!("The value of the element at index {index} is: {element}");
}
Output:
thread 'main' panicked at src/main.rs:19:19:
index out of bounds: the len is 5 but the index is 10
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Rust prevents buffer overflows by bounds checking at runtime.