Complete Guide to Rust Data Types

Introduction to Rust Data Types

Rust is a statically typed language, meaning it must know the types of all variables at compile time. The compiler can usually infer the type based on the value and how it's used, but sometimes you need to annotate types explicitly. Rust's type system is designed to be expressive while maintaining memory safety and performance.

Key Concepts

  • Static Typing: All types are known at compile time
  • Type Inference: Compiler can deduce types in most cases
  • Strong Typing: Limited implicit conversions between types
  • Zero-Cost Abstractions: Types don't add runtime overhead
  • Memory Safety: Type system prevents many common bugs

1. Scalar Types

Scalar types represent a single value. Rust has four primary scalar types: integers, floating-point numbers, booleans, and characters.

Integer Types

fn main() {
// Signed integers (can be negative)
let a: i8 = -128;           // -128 to 127
let b: i16 = -32768;        // -32768 to 32767
let c: i32 = -2147483648;   // -2^31 to 2^31-1 (default)
let d: i64 = -9223372036854775808;
let e: i128 = -170141183460469231731687303715884105728;
let f: isize = -100;        // Architecture-dependent (32 or 64 bits)
// Unsigned integers (non-negative)
let g: u8 = 255;             // 0 to 255
let h: u16 = 65535;          // 0 to 65535
let i: u32 = 4294967295;     // 0 to 2^32-1
let j: u64 = 18446744073709551615;
let k: u128 = 340282366920938463463374607431768211455;
let l: usize = 100;          // Architecture-dependent
// Integer literals
let decimal = 98_222;        // Decimal with underscores for readability
let hex = 0xff;              // Hexadecimal
let octal = 0o77;            // Octal
let binary = 0b1111_0000;    // Binary
let byte = b'A';             // Byte (u8 only)
println!("Decimal: {}", decimal);
println!("Hex: {}", hex);
println!("Octal: {}", octal);
println!("Binary: {:b}", binary);
println!("Byte: {}", byte);
// Integer operations
let sum = 5 + 10;
let difference = 95.5 - 4.3;
let product = 4 * 30;
let quotient = 56.7 / 32.2;
let remainder = 43 % 5;
// Integer overflow behavior (debug vs release)
let mut x: u8 = 255;
// In debug builds, this would panic
// In release builds, it wraps to 0
// x = x + 1; // Uncomment to see behavior
// Safe alternatives to handle overflow
let y = 255u8;
println!("Checked add: {:?}", y.checked_add(1));     // None
println!("Saturating add: {}", y.saturating_add(1)); // 255
println!("Wrapping add: {}", y.wrapping_add(1));     // 0
println!("Overflowing add: {:?}", y.overflowing_add(1)); // (0, true)
}

Floating-Point Types

fn main() {
// f32 (32-bit float, single precision)
let f32_val: f32 = 3.14;
// f64 (64-bit float, double precision) - default
let f64_val = 3.141592653589793;
// Scientific notation
let scientific = 2.5e5;      // 250000
let scientific_neg = 1.5e-3; // 0.0015
println!("f32: {}", f32_val);
println!("f64: {}", f64_val);
println!("Scientific: {}", scientific);
// Floating-point operations
let sum = 5.0 + 10.2;
let difference = 95.5 - 4.3;
let product = 4.0 * 30.0;
let quotient = 56.7 / 32.2;
// Special floating-point values
let infinity = f64::INFINITY;
let neg_infinity = f64::NEG_INFINITY;
let nan = f64::NAN;
let min = f64::MIN;
let max = f64::MAX;
let min_positive = f64::MIN_POSITIVE;
println!("Infinity: {}", infinity);
println!("NaN: {}", nan);
println!("NaN check: {}", nan.is_nan());
// Floating-point precision issues
let result = 0.1 + 0.2;
println!("0.1 + 0.2 = {}", result); // 0.30000000000000004
// Comparing floats (use epsilon)
let a = 0.1 + 0.2;
let b = 0.3;
let epsilon = f64::EPSILON;
if (a - b).abs() < epsilon {
println!("Approximately equal");
}
// Rounding
let num = 3.14159;
println!("Round: {}", num.round());     // 3
println!("Floor: {}", num.floor());     // 3
println!("Ceil: {}", num.ceil());       // 4
println!("Trunc: {}", num.trunc());     // 3
println!("Fract: {}", num.fract());     // 0.14159
}

Boolean Type

fn main() {
// Boolean values
let t = true;
let f: bool = false;
// Boolean operations
let and = t && f;      // Logical AND
let or = t || f;       // Logical OR
let not = !t;          // Logical NOT
println!("AND: {}", and);
println!("OR: {}", or);
println!("NOT: {}", not);
// Boolean from comparisons
let greater = 5 > 3;
let less = 5 < 3;
let equal = 5 == 5;
let not_equal = 5 != 3;
// Boolean in conditions
if greater {
println!("5 is greater than 3");
}
// Boolean as integer (cast)
let bool_as_int = t as i32;  // 1
let bool_as_int2 = f as i32; // 0
println!("true as i32: {}", bool_as_int);
// Boolean methods
let flag = true;
println!("then: {}", flag.then(|| 42).unwrap()); // 42
println!("then_some: {}", flag.then_some("yes").unwrap()); // "yes"
}

Character Type

fn main() {
// Characters are 4 bytes (Unicode scalar values)
let c = 'z';
let z = 'ℤ';
let heart_eyed_cat = '😻';
println!("c: {}", c);
println!("z: {}", z);
println!("cat: {}", heart_eyed_cat);
// Character literals with escapes
let tab = '\t';
let newline = '\n';
let carriage_return = '\r';
let single_quote = '\'';
let double_quote = '"';
let backslash = '\\';
// Unicode escapes
let omega = '\u{03A9}';  // Ω
let smiley = '\u{1F600}'; // 😀
println!("Omega: {}", omega);
println!("Smiley: {}", smiley);
// Character operations
let ch = 'a';
println!("Is alphabetic: {}", ch.is_alphabetic());
println!("Is numeric: {}", ch.is_numeric());
println!("Is whitespace: {}", ch.is_whitespace());
println!("To uppercase: {}", ch.to_uppercase().collect::<String>());
// Character conversions
let digit = '5';
let digit_num = digit.to_digit(10).unwrap();
println!("Digit as number: {}", digit_num);
// Character from u8
let byte_char = 65 as char;
println!("65 as char: {}", byte_char); // 'A'
// Character size
println!("Size of char: {} bytes", std::mem::size_of::<char>()); // 4 bytes
}

2. Compound Types

Compound types group multiple values into one type.

Tuples

fn main() {
// Tuple declaration
let tup: (i32, f64, char) = (500, 6.4, 'q');
// Type inference
let tup2 = (500, 6.4, 'q');
// Accessing tuple elements
let (x, y, z) = tup;  // Destructuring
println!("x: {}, y: {}, z: {}", x, y, z);
// Index access
let first = tup.0;
let second = tup.1;
let third = tup.2;
println!("first: {}, second: {}, third: {}", first, second, third);
// Single-element tuple (note the comma)
let single = (5,);     // Tuple with one element
let not_tuple = (5);   // Just a number in parentheses
// Nested tuples
let nested = (1, (2, 3), 4);
println!("nested.1.1: {}", (nested.1).1); // 3
// Unit type - empty tuple
let unit = ();
println!("Unit type: {:?}", unit);
// Functions return unit implicitly
fn returns_unit() {}
// Tuples in match expressions
let pair = (1, 2);
match pair {
(0, y) => println!("First is 0, second is {}", y),
(x, 0) => println!("Second is 0, first is {}", x),
(x, y) => println!("Both non-zero: {}, {}", x, y),
}
// Tuple as function argument
fn sum_tuple(t: (i32, i32)) -> i32 {
t.0 + t.1
}
let result = sum_tuple((5, 10));
println!("Sum: {}", result);
// Tuple size
println!("Size of tuple (i32, f64): {} bytes", 
std::mem::size_of::<(i32, f64)>());
}

Arrays

fn main() {
// Fixed-size arrays (stack allocated)
let arr: [i32; 5] = [1, 2, 3, 4, 5];
// Type inference
let arr2 = [1, 2, 3, 4, 5];
// Array with same value for all elements
let zeros = [0; 10];      // [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
let threes = [3; 5];      // [3, 3, 3, 3, 3]
// Accessing array elements
let first = arr[0];
let second = arr[1];
println!("First: {}, Second: {}", first, second);
// Array length
let length = arr.len();
println!("Array length: {}", length);
// Array slicing
let slice = &arr[1..4];    // [2, 3, 4]
let full_slice = &arr[..]; // entire array
let from_start = &arr[2..]; // [3, 4, 5]
let to_end = &arr[..3];     // [1, 2, 3]
println!("Slice: {:?}", slice);
// Array iteration
for element in arr.iter() {
println!("Element: {}", element);
}
// Mutable array
let mut mut_arr = [1, 2, 3, 4, 5];
mut_arr[0] = 10;
println!("Modified array: {:?}", mut_arr);
// Array bounds checking
// let out_of_bounds = arr[10]; // This would panic at runtime
// Safe access with get()
match arr.get(10) {
Some(value) => println!("Value: {}", value),
None => println!("Index out of bounds"),
}
// Multidimensional arrays
let matrix: [[i32; 3]; 2] = [
[1, 2, 3],
[4, 5, 6],
];
println!("matrix[0][1]: {}", matrix[0][1]); // 2
// Array operations
let arr1 = [1, 2, 3];
let arr2 = [4, 5, 6];
// Arrays implement useful traits
println!("Is empty: {}", arr1.is_empty());
println!("First: {:?}", arr1.first());
println!("Last: {:?}", arr1.last());
// Array size at compile time
const ARRAY_SIZE: usize = 5;
let fixed: [i32; ARRAY_SIZE] = [0; ARRAY_SIZE];
println!("Fixed size array: {:?}", fixed);
}

3. String Types

String vs &str

fn main() {
// String (owned, heap-allocated, mutable)
let mut s = String::new();
s.push_str("hello");
s.push(' ');
s.push_str("world");
println!("String: {}", s);
// String from literal
let s2 = String::from("hello world");
let s3 = "hello world".to_string();
// &str (string slice, borrowed, immutable)
let slice: &str = "hello world";  // String literal
let slice2 = &s2[..];              // Slice from String
// Differences and conversions
let string = String::from("hello");
let str_slice: &str = &string;     // &String -> &str
let new_string = str_slice.to_string(); // &str -> String
let new_string2 = String::from(str_slice);
// String operations
let mut s = String::from("foo");
s.push_str("bar");                  // Append string slice
s.push('!');                         // Append character
// Concatenation
let s1 = String::from("Hello, ");
let s2 = String::from("world!");
let s3 = s1 + &s2;                   // s1 is moved here
println!("s3: {}", s3);
// Better concatenation with format!
let s1 = String::from("tic");
let s2 = String::from("tac");
let s3 = String::from("toe");
let s = format!("{}-{}-{}", s1, s2, s3);
println!("s: {}", s);
// String indexing (not allowed)
// let ch = s[0]; // ERROR: cannot index into String
// String slicing (careful with UTF-8)
let hello = "Здравствуйте";
let slice = &hello[0..4];            // "Зд" (each char is 2 bytes)
println!("Slice: {}", slice);
// Iterating over strings
for c in "नमस्ते".chars() {
println!("Char: {}", c);
}
for b in "hello".bytes() {
println!("Byte: {}", b);
}
}

String Methods

fn main() {
let mut s = String::from("  Hello, World!  ");
// Trimming
println!("Trim: '{}'", s.trim());
println!("Trim start: '{}'", s.trim_start());
println!("Trim end: '{}'", s.trim_end());
// Case conversion
println!("Lowercase: {}", s.to_lowercase());
println!("Uppercase: {}", s.to_uppercase());
// Checking content
println!("Contains 'Hello': {}", s.contains("Hello"));
println!("Starts with '  ': {}", s.starts_with("  "));
println!("Ends with '!  ': {}", s.ends_with("!  "));
// Finding
println!("Find 'World': {:?}", s.find("World"));
println!("Find 'xyz': {:?}", s.find("xyz"));
// Replacing
println!("Replace: {}", s.replace("World", "Rust"));
println!("Replace pattern: {}", s.replace(char::is_whitespace, "_"));
// Splitting
let parts: Vec<&str> = s.split(',').collect();
println!("Split: {:?}", parts);
let words: Vec<&str> = s.split_whitespace().collect();
println!("Words: {:?}", words);
// Lines
let multiline = "line1\nline2\nline3";
for line in multiline.lines() {
println!("Line: {}", line);
}
// Substrings
let sub = &s[2..7]; // "Hello"
println!("Substring: {}", sub);
// Length and capacity
println!("Length: {}", s.len());
println!("Capacity: {}", s.capacity());
println!("Is empty: {}", s.is_empty());
// Modifying in-place
s.clear();
println!("After clear: '{}'", s);
// Building strings efficiently
let mut builder = String::with_capacity(100);
for i in 0..10 {
builder.push_str(&format!("item-{} ", i));
}
println!("Builder: {}", builder);
}

4. Vector Types

Vec

fn main() {
// Creating vectors
let mut v: Vec<i32> = Vec::new();
let v2 = vec![1, 2, 3, 4, 5];
let v3 = vec![0; 10]; // Ten zeros
// Adding elements
v.push(5);
v.push(6);
v.push(7);
println!("v: {:?}", v);
// Accessing elements
let third: &i32 = &v[2];
println!("Third element: {}", third);
match v.get(2) {
Some(third) => println!("Third element: {}", third),
None => println!("No third element"),
}
// Safe access
match v.get(10) {
Some(value) => println!("Value: {}", value),
None => println!("Index out of bounds"),
}
// Iterating
for i in &v {
println!("Element: {}", i);
}
// Mutable iteration
for i in &mut v {
*i *= 2;
}
println!("Doubled: {:?}", v);
// Removing elements
let last = v.pop(); // Removes last element
println!("Popped: {:?}, v: {:?}", last, v);
let first = v.remove(0); // Removes element at index
println!("Removed first: {}, v: {:?}", first, v);
// Inserting
v.insert(0, 10);
v.insert(2, 20);
println!("After insert: {:?}", v);
// Vector operations
println!("Length: {}", v.len());
println!("Capacity: {}", v.capacity());
println!("Is empty: {}", v.is_empty());
// Resizing
v.resize(5, 0);
println!("After resize: {:?}", v);
// Sorting
let mut unsorted = vec![3, 1, 4, 1, 5, 9, 2, 6];
unsorted.sort();
println!("Sorted: {:?}", unsorted);
// Reverse
unsorted.reverse();
println!("Reversed: {:?}", unsorted);
// Filtering and transforming
let numbers = vec![1, 2, 3, 4, 5, 6];
let evens: Vec<_> = numbers.iter()
.filter(|&&x| x % 2 == 0)
.collect();
println!("Evens: {:?}", evens);
let squares: Vec<_> = numbers.iter()
.map(|&x| x * x)
.collect();
println!("Squares: {:?}", squares);
// Vector of different types using enum
#[derive(Debug)]
enum MixedType {
Int(i32),
Float(f64),
Text(String),
}
let mixed = vec![
MixedType::Int(42),
MixedType::Float(3.14),
MixedType::Text(String::from("hello")),
];
println!("Mixed: {:?}", mixed);
}

5. Struct Types

Basic Structs

// Named field struct
struct User {
username: String,
email: String,
sign_in_count: u64,
active: bool,
}
// Tuple struct (named tuple)
struct Color(i32, i32, i32);
struct Point(i32, i32, i32);
// Unit-like struct (no fields)
struct AlwaysEqual;
fn main() {
// Creating instances
let user1 = User {
email: String::from("[email protected]"),
username: String::from("someusername123"),
active: true,
sign_in_count: 1,
};
println!("User email: {}", user1.email);
// Mutable instance
let mut user2 = User {
email: String::from("[email protected]"),
username: String::from("anotheruser"),
active: true,
sign_in_count: 1,
};
user2.email = String::from("[email protected]");
// Struct update syntax
let user3 = User {
email: String::from("[email protected]"),
username: String::from("thirduser"),
..user1  // Copy remaining fields from user1
};
// Tuple structs
let black = Color(0, 0, 0);
let origin = Point(0, 0, 0);
println!("Black RGB: ({}, {}, {})", black.0, black.1, black.2);
// Unit-like struct
let subject = AlwaysEqual;
// Destructuring
let Point(x, y, z) = origin;
println!("Point: ({}, {}, {})", x, y, z);
}
// Factory function
fn build_user(email: String, username: String) -> User {
User {
email,
username,
active: true,
sign_in_count: 1,
}
}

Struct Methods

#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
// Implementation block
impl Rectangle {
// Method (takes self)
fn area(&self) -> u32 {
self.width * self.height
}
// Method with parameters
fn can_hold(&self, other: &Rectangle) -> bool {
self.width > other.width && self.height > other.height
}
// Associated function (doesn't take self)
fn square(size: u32) -> Rectangle {
Rectangle {
width: size,
height: size,
}
}
// Getter
fn width(&self) -> u32 {
self.width
}
// Setter (with validation)
fn set_width(&mut self, width: u32) {
if width > 0 {
self.width = width;
}
}
// Method that takes ownership
fn destroy(self) {
println!("Destroying rectangle: {:?}", self);
// self dropped here
}
}
// Multiple impl blocks
impl Rectangle {
fn is_square(&self) -> bool {
self.width == self.height
}
fn diagonal(&self) -> f64 {
let w = self.width as f64;
let h = self.height as f64;
(w * w + h * h).sqrt()
}
}
fn main() {
let rect = Rectangle {
width: 30,
height: 50,
};
println!("Area: {}", rect.area());
println!("Width: {}", rect.width());
println!("Is square: {}", rect.is_square());
println!("Diagonal: {:.2}", rect.diagonal());
let square = Rectangle::square(20);
println!("Square area: {}", square.area());
println!("Square holds rect: {}", square.can_hold(&rect));
let mut rect2 = Rectangle { width: 10, height: 10 };
rect2.set_width(15);
println!("Updated width: {}", rect2.width());
// rect2.destroy(); // Uncomment to move ownership
// println!("{:?}", rect2); // Error: rect2 moved
}

6. Enum Types

Basic Enums

// Simple enum
enum IpAddrKind {
V4,
V6,
}
// Enum with data
enum IpAddr {
V4(u8, u8, u8, u8),
V6(String),
}
// Enum with different variants
enum Message {
Quit,                       // No data
Move { x: i32, y: i32 },    // Anonymous struct
Write(String),              // Single string
ChangeColor(i32, i32, i32), // Tuple
}
impl Message {
fn call(&self) {
match self {
Message::Quit => println!("Quit"),
Message::Move { x, y } => println!("Move to ({}, {})", x, y),
Message::Write(text) => println!("Write: {}", text),
Message::ChangeColor(r, g, b) => {
println!("Change color to RGB({}, {}, {})", r, g, b);
}
}
}
}
fn main() {
let four = IpAddrKind::V4;
let six = IpAddrKind::V6;
let home = IpAddr::V4(127, 0, 0, 1);
let loopback = IpAddr::V6(String::from("::1"));
let m1 = Message::Quit;
let m2 = Message::Move { x: 10, y: 20 };
let m3 = Message::Write(String::from("hello"));
let m4 = Message::ChangeColor(255, 0, 0);
m1.call();
m2.call();
m3.call();
m4.call();
}

Option Enum

fn main() {
// Option<T> is built into Rust prelude
let some_number = Some(5);
let some_string = Some("a string");
let absent_number: Option<i32> = None;
// Option vs null-safe operations
let x: i8 = 5;
let y: Option<i8> = Some(5);
// Need to handle Option before using value
match y {
Some(value) => println!("Sum: {}", x + value),
None => println!("No value"),
}
// if let syntax
if let Some(value) = y {
println!("Value: {}", value);
}
// Option combinators
let maybe_value: Option<i32> = Some(10);
let doubled = maybe_value.map(|x| x * 2);
println!("Doubled: {:?}", doubled);
let filtered = maybe_value.filter(|&x| x > 5);
println!("Filtered: {:?}", filtered);
let or_else = maybe_value.or(Some(20));
println!("Or else: {:?}", or_else);
let unwrapped = maybe_value.unwrap_or(0);
println!("Unwrapped or default: {}", unwrapped);
// Chaining options
let result = Some(5)
.map(|x| x * 2)
.filter(|&x| x > 5)
.and_then(|x| if x < 20 { Some(x) } else { None });
println!("Chained result: {:?}", result);
}

Result Enum

use std::num::ParseIntError;
fn main() {
// Result<T, E> for error handling
let success: Result<i32, &str> = Ok(42);
let failure: Result<i32, &str> = Err("Something went wrong");
// Parsing returns Result
let parsed = "42".parse::<i32>();
match parsed {
Ok(num) => println!("Parsed: {}", num),
Err(e) => println!("Error: {}", e),
}
// Result combinators
let result = "42".parse::<i32>();
let doubled = result.map(|x| x * 2);
println!("Doubled: {:?}", doubled);
let handled = result.map_err(|e| format!("Parse error: {}", e));
println!("Handled: {:?}", handled);
let unwrapped = result.unwrap_or(0);
println!("Unwrapped or default: {}", unwrapped);
// Working with multiple Results
let nums = vec!["42", "24", "abc", "12"];
let parsed_nums: Vec<Result<i32, ParseIntError>> = nums
.iter()
.map(|s| s.parse())
.collect();
println!("Parsed results: {:?}", parsed_nums);
// Filter out errors
let valid_nums: Vec<i32> = nums
.iter()
.filter_map(|s| s.parse().ok())
.collect();
println!("Valid numbers: {:?}", valid_nums);
}
// Function returning Result
fn divide(numerator: f64, denominator: f64) -> Result<f64, String> {
if denominator == 0.0 {
Err(String::from("Division by zero"))
} else {
Ok(numerator / denominator)
}
}
// Using ? operator for error propagation
fn calculate() -> Result<f64, String> {
let a = divide(10.0, 2.0)?;
let b = divide(a, 3.0)?;
let c = divide(b, 0.0)?; // This will propagate error
Ok(c)
}

7. Reference Types

References and Borrowing

fn main() {
let s = String::from("hello");
// Immutable reference
let r1 = &s;
let r2 = &s; // Multiple immutable references allowed
println!("r1: {}, r2: {}", r1, r2);
// Mutable reference
let mut s2 = String::from("hello");
let r3 = &mut s2;
r3.push_str(", world");
// let r4 = &s2; // Can't borrow immutably while mutably borrowed
// Dangling references (prevented by compiler)
// let ref_to_nothing = dangle(); // ERROR
// Reference rules
let mut data = String::from("data");
{
let r1 = &data; // Immutable borrow
let r2 = &data; // Another immutable borrow
println!("r1: {}, r2: {}", r1, r2);
// r1 and r2 go out of scope
}
let r3 = &mut data; // Now mutable borrow is allowed
r3.push_str(" modified");
// Reference to reference
let x = 5;
let y = &x;
let z = &&x;
println!("{}", **z); // Dereferencing
}
// fn dangle() -> &String { // Returns reference to String
//     let s = String::from("hello");
//     &s // s goes out of scope, reference would be dangling
// }

Smart Pointers

use std::rc::Rc;
use std::cell::RefCell;
use std::sync::{Arc, Mutex};
fn main() {
// Box<T> - heap allocation
let b = Box::new(5);
println!("b = {}", b);
// Box with recursive type
#[derive(Debug)]
enum List {
Cons(i32, Box<List>),
Nil,
}
let list = List::Cons(1, 
Box::new(List::Cons(2, 
Box::new(List::Cons(3, 
Box::new(List::Nil))))));
println!("{:?}", list);
// Rc<T> - reference counted (single-threaded)
use List2::{Cons as Cons2, Nil as Nil2};
#[derive(Debug)]
enum List2 {
Cons(i32, Rc<List2>),
Nil,
}
let a = Rc::new(Cons2(5, Rc::new(Cons2(10, Rc::new(Nil2)))));
println!("count after creating a = {}", Rc::strong_count(&a));
let b = Cons2(3, Rc::clone(&a));
println!("count after creating b = {}", Rc::strong_count(&a));
{
let c = Cons2(4, Rc::clone(&a));
println!("count after creating c = {}", Rc::strong_count(&a));
}
println!("count after c goes out of scope = {}", Rc::strong_count(&a));
// RefCell<T> - interior mutability
let cell = RefCell::new(5);
// Borrow mutably at runtime
*cell.borrow_mut() += 1;
println!("cell: {:?}", cell);
// Multiple borrows checked at runtime
{
let r1 = cell.borrow();
let r2 = cell.borrow();
// let r3 = cell.borrow_mut(); // This would panic at runtime
println!("r1: {}, r2: {}", r1, r2);
}
// Arc<T> + Mutex<T> for thread-safe sharing
use std::thread;
let counter = Arc::new(Mutex::new(0));
let mut handles = vec![];
for _ in 0..10 {
let counter = Arc::clone(&counter);
handles.push(thread::spawn(move || {
let mut num = counter.lock().unwrap();
*num += 1;
}));
}
for handle in handles {
handle.join().unwrap();
}
println!("Result: {}", *counter.lock().unwrap());
}

8. Function Types

Function Pointers

fn add_one(x: i32) -> i32 {
x + 1
}
fn do_twice(f: fn(i32) -> i32, arg: i32) -> i32 {
f(arg) + f(arg)
}
fn main() {
// Function pointer
let f: fn(i32) -> i32 = add_one;
let result = f(5);
println!("Result: {}", result);
// Pass function as argument
let result = do_twice(add_one, 5);
println!("Twice: {}", result);
// Function pointer from closure (no capture)
let f2: fn(i32) -> i32 = |x| x + 1;
println!("Closure as fn: {}", f2(5));
// Function pointers in collections
let functions: Vec<fn(i32) -> i32> = vec![
add_one,
|x| x * 2,
|x| x * x,
];
for (i, f) in functions.iter().enumerate() {
println!("Function {} result: {}", i, f(5));
}
}
// Function returning function pointer
fn get_math_function(op: &str) -> Option<fn(i32, i32) -> i32> {
match op {
"add" => Some(|a, b| a + b),
"sub" => Some(|a, b| a - b),
"mul" => Some(|a, b| a * b),
"div" => Some(|a, b| a / b),
_ => None,
}
}

9. Type Aliases

// Type alias
type Kilometers = i32;
type Thunk = Box<dyn Fn() + Send + 'static>;
fn main() {
let x: i32 = 5;
let y: Kilometers = 5;
println!("x + y = {}", x + y);
// Type alias for complex types
type Result<T> = std::result::Result<T, std::io::Error>;
// Using in functions
fn write_to_file() -> Result<()> {
Ok(())
}
// Type alias for closures
type Callback = Box<dyn Fn(i32) -> i32>;
let callbacks: Vec<Callback> = vec![
Box::new(|x| x * 2),
Box::new(|x| x + 10),
Box::new(|x| x * x),
];
for callback in callbacks {
println!("Callback result: {}", callback(5));
}
}
// Type alias with generics
type HashMap<K, V> = std::collections::HashMap<K, V>;
// Usage in other module
fn example() {
let mut map: HashMap<String, i32> = HashMap::new();
map.insert("key".to_string(), 42);
}

10. Never Type

#![allow(unused)]
fn main() {
// The never type ! represents computations that never complete
// Functions that never return
fn loop_forever() -> ! {
loop {
// infinite loop
}
}
// panic! returns !
fn example() {
let x = if true {
5
} else {
panic!("This never happens");
// panic! returns !, so type checking works
};
println!("x = {}", x);
}
// continue returns ! in loops
let mut count = 0;
loop {
count += 1;
if count < 5 {
continue; // continue returns !
}
break;
}
// The never type in match arms
let value: Option<i32> = None;
match value {
Some(x) => println!("Got: {}", x),
None => loop {}, // loop returns !
}
// exit function returns !
use std::process;
if false {
process::exit(1); // This function returns !
}
}

11. Custom Type Derivation

// Automatically implement common traits
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
struct Point {
x: i32,
y: i32,
}
#[derive(Debug, Clone, PartialEq)]
enum Shape {
Circle { radius: f64 },
Rectangle { width: f64, height: f64 },
}
fn main() {
let p1 = Point { x: 10, y: 20 };
let p2 = p1; // Copy because of Copy trait
let p3 = p1.clone(); // Clone
println!("p1: {:?}", p1);
println!("p1 == p2: {}", p1 == p2);
let circle = Shape::Circle { radius: 5.0 };
let circle2 = circle.clone();
println!("circle: {:?}", circle);
println!("circle == circle2: {}", circle == circle2);
}
// Default trait
#[derive(Debug, Default)]
struct Config {
host: String,
port: u16,
timeout: Option<u64>,
}
fn default_example() {
let config = Config::default();
println!("Default config: {:?}", config);
let custom = Config {
port: 8080,
..Default::default()
};
}

12. Type Conversion

From and Into Traits

use std::convert::From;
#[derive(Debug)]
struct Number {
value: i32,
}
// From trait
impl From<i32> for Number {
fn from(item: i32) -> Self {
Number { value: item }
}
}
// From for custom types
struct Temperature {
celsius: f64,
}
impl Temperature {
fn from_fahrenheit(f: f64) -> Self {
Temperature {
celsius: (f - 32.0) * 5.0 / 9.0,
}
}
fn from_kelvin(k: f64) -> Self {
Temperature {
celsius: k - 273.15,
}
}
}
fn main() {
// Using From
let num = Number::from(30);
println!("Number from i32: {:?}", num);
// Using Into (usually type annotation needed)
let num2: Number = 30.into();
println!("Number from into: {:?}", num2);
// Standard conversions
let int_val = 5;
let float_val = int_val as f64;
let string_val = int_val.to_string();
let parsed_int = "42".parse::<i32>().unwrap();
// TryFrom for fallible conversions
use std::convert::TryFrom;
let large = 256;
match i8::try_from(large) {
Ok(val) => println!("Converted: {}", val),
Err(e) => println!("Failed: {}", e),
}
// Custom conversions
let boiling = Temperature::from_celsius(100.0);
let freezing_f = Temperature::from_fahrenheit(32.0);
let absolute_zero = Temperature::from_kelvin(0.0);
}
impl Temperature {
fn from_celsius(c: f64) -> Self {
Temperature { celsius: c }
}
fn to_fahrenheit(&self) -> f64 {
self.celsius * 9.0 / 5.0 + 32.0
}
fn to_kelvin(&self) -> f64 {
self.celsius + 273.15
}
}

13. Advanced Type Features

Associated Types

// Associated types in traits
trait Container {
type Item;
fn add(&mut self, item: Self::Item);
fn get(&self, index: usize) -> Option<&Self::Item>;
}
struct MyVec<T> {
items: Vec<T>,
}
impl<T> Container for MyVec<T> {
type Item = T;
fn add(&mut self, item: T) {
self.items.push(item);
}
fn get(&self, index: usize) -> Option<&T> {
self.items.get(index)
}
}
fn main() {
let mut vec = MyVec { items: Vec::new() };
vec.add(42);
vec.add(100);
if let Some(val) = vec.get(0) {
println!("First item: {}", val);
}
}

Type Bounds and Where Clauses

use std::fmt::Display;
// Generic function with type bounds
fn print_and_return<T: Display>(item: T) -> T {
println!("Item: {}", item);
item
}
// Multiple bounds
fn compare_and_print<T: PartialOrd + Display>(a: T, b: T) {
if a > b {
println!("{} is greater than {}", a, b);
} else {
println!("{} is less than or equal to {}", a, b);
}
}
// Where clause for complex bounds
fn complex_function<T, U>(t: T, u: U) -> i32
where
T: Display + Clone,
U: Clone + std::fmt::Debug,
{
println!("t: {}", t);
println!("u: {:?}", u);
42
}
// Trait objects
trait Drawable {
fn draw(&self);
}
struct Circle {
radius: f64,
}
impl Drawable for Circle {
fn draw(&self) {
println!("Drawing circle with radius {}", self.radius);
}
}
struct Square {
side: f64,
}
impl Drawable for Square {
fn draw(&self) {
println!("Drawing square with side {}", self.side);
}
}
fn draw_all(shapes: Vec<Box<dyn Drawable>>) {
for shape in shapes {
shape.draw();
}
}
fn main() {
print_and_return(42);
compare_and_print(10, 5);
let shapes: Vec<Box<dyn Drawable>> = vec![
Box::new(Circle { radius: 5.0 }),
Box::new(Square { side: 3.0 }),
];
draw_all(shapes);
}

Conclusion

Rust's type system provides a rich set of tools for creating safe, expressive, and efficient programs:

Key Takeaways

  1. Scalar Types: Integers, floats, booleans, and characters for basic values
  2. Compound Types: Tuples and arrays for grouping values
  3. String Types: String (owned) and &str (borrowed) for text
  4. Collection Types: Vectors for dynamic arrays
  5. Custom Types: Structs and enums for domain modeling
  6. Option and Result: For handling optional values and errors
  7. Reference Types: Borrowing and smart pointers for memory management
  8. Function Types: Function pointers and closures
  9. Type Aliases: For readability and abstraction
  10. Type Conversion: From/Into traits for safe conversions

Best Practices

  • Use appropriate integer types (prefer i32/u32 unless size matters)
  • Use f64 as default floating-point type
  • Prefer String for owned text, &str for string slices
  • Use enums for types with a fixed set of variants
  • Derive common traits when appropriate
  • Use type aliases for complex types
  • Leverage the type system to prevent bugs at compile time

Rust's type system is one of its strongest features, providing compile-time guarantees that would be runtime checks in other languages, all without runtime overhead.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper