Introduction to Functions in Rust
Functions are the fundamental building blocks of Rust programs. They encapsulate behavior, promote code reuse, and help organize code into logical, manageable pieces. Rust's function system combines elements from functional and systems programming, offering powerful features like pattern matching in parameters, multiple return values, and function pointers.
Key Concepts
- Function Declaration: Using the
fnkeyword - Parameters: Typed inputs to functions
- Return Values: Explicit or implicit returns
- Statements vs Expressions: Understanding the difference
- Function Types: First-class functions and closures
- Safety: Borrowing rules apply to function boundaries
1. Basic Function Syntax
Simple Functions
// Basic function with no parameters and no return fn greet() { println!("Hello, world!"); } // Function with parameters fn greet_person(name: &str) { println!("Hello, {}!", name); } // Function with return value fn add(a: i32, b: i32) -> i32 { a + b // Implicit return (no semicolon) } // Function with explicit return fn multiply(a: i32, b: i32) -> i32 { return a * b; // Explicit return (with semicolon) } fn main() { greet(); greet_person("Alice"); let sum = add(5, 3); println!("Sum: {}", sum); let product = multiply(4, 7); println!("Product: {}", product); } Function Ordering
// Functions can be defined after they're called fn main() { let result = calculate(10, 5); println!("Result: {}", result); call_before_definition(); // Works due to hoisting } // Definition after main fn calculate(x: i32, y: i32) -> i32 { x * y + y } fn call_before_definition() { println!("This works because functions are hoisted"); } // Nested functions (allowed) fn outer_function() { println!("Outer function"); fn inner_function() { println!("Inner function"); } inner_function(); // Can only be called within outer_function } 2. Parameters and Arguments
Parameter Patterns
// Basic parameters fn basic_params(x: i32, y: f64, z: &str) { println!("x: {}, y: {}, z: {}", x, y, z); } // Pattern matching in parameters fn print_coordinates(&(x, y): &(i32, i32)) { println!("Coordinates: ({}, {})", x, y); } // Destructuring structs struct Point { x: i32, y: i32, } fn print_point(Point { x, y }: &Point) { println!("Point at ({}, {})", x, y); } // Multiple patterns enum Message { Quit, Move { x: i32, y: i32 }, Write(String), } fn handle_message(msg: Message) { match msg { Message::Quit => println!("Quit"), Message::Move { x, y } => println!("Move to ({}, {})", x, y), Message::Write(text) => println!("Write: {}", text), } } fn main() { basic_params(10, 3.14, "hello"); let tuple = (5, 10); print_coordinates(&tuple); let point = Point { x: 15, y: 25 }; print_point(&point); handle_message(Message::Move { x: 10, y: 20 }); } Reference Parameters
// Immutable reference fn calculate_length(s: &String) -> usize { s.len() } // Mutable reference fn append_world(s: &mut String) { s.push_str(", world"); } // Multiple references fn process_strings(s1: &String, s2: &String) -> String { format!("{} {}", s1, s2) } // Slice parameters fn first_word(s: &str) -> &str { let bytes = s.as_bytes(); for (i, &item) in bytes.iter().enumerate() { if item == b' ' { return &s[0..i]; } } &s[..] } fn main() { let s = String::from("hello"); let len = calculate_length(&s); println!("Length: {}", len); let mut s = String::from("hello"); append_world(&mut s); println!("Modified: {}", s); let s1 = String::from("hello"); let s2 = String::from("world"); let result = process_strings(&s1, &s2); println!("Combined: {}", result); let sentence = String::from("hello world"); let word = first_word(&sentence); println!("First word: {}", word); } 3. Return Values
Different Return Patterns
// Single return value fn square(x: i32) -> i32 { x * x } // Multiple return values via tuple fn divide(dividend: i32, divisor: i32) -> (i32, i32) { (dividend / divisor, dividend % divisor) } // Named return values (not like Go, just for documentation) fn get_coordinates() -> (x: i32, y: i32) { (10, 20) // Return tuple, names are for documentation only } // Returning references fn get_first_element(vec: &[i32]) -> Option<&i32> { vec.first() } // Returning Result fn safe_divide(numerator: f64, denominator: f64) -> Result<f64, String> { if denominator == 0.0 { Err("Division by zero".to_string()) } else { Ok(numerator / denominator) } } // Returning Option fn find_in_vector(vec: &[i32], target: i32) -> Option<usize> { vec.iter().position(|&x| x == target) } fn main() { println!("Square: {}", square(5)); let (quotient, remainder) = divide(17, 5); println!("17 / 5 = {} remainder {}", quotient, remainder); let (x, y) = get_coordinates(); println!("Coordinates: ({}, {})", x, y); let numbers = vec![1, 2, 3, 4, 5]; if let Some(first) = get_first_element(&numbers) { println!("First element: {}", first); } match safe_divide(10.0, 2.0) { Ok(result) => println!("Division result: {}", result), Err(e) => println!("Error: {}", e), } match find_in_vector(&numbers, 3) { Some(pos) => println!("Found at position: {}", pos), None => println!("Not found"), } } 4. Statements vs Expressions
Understanding the Difference
// Statements perform actions but don't return values // Expressions evaluate to a value fn demonstrate_statements_expressions() { // Statement: variable declaration let x = 5; // Expression: 5 + 3 evaluates to 8 let y = 5 + 3; // Statement: println! macro call println!("x = {}, y = {}", x, y); // Block as expression let z = { let a = 10; let b = 20; a + b // This is an expression (no semicolon) }; println!("z = {}", z); // If as expression let condition = true; let result = if condition { 100 // Expression } else { 200 // Expression }; println!("result = {}", result); // Match as expression let number = 3; let description = match number { 1 => "one", 2 => "two", _ => "other", }; println!("description = {}", description); // Loop as expression let mut counter = 0; let loop_result = loop { counter += 1; if counter == 5 { break counter * 2; // Expression returning value } }; println!("loop_result = {}", loop_result); } Function Bodies as Expressions
// Function body is a block expression fn add_v1(a: i32, b: i32) -> i32 { a + b // Expression (no semicolon) -> implicit return } fn add_v2(a: i32, b: i32) -> i32 { return a + b; // Statement with explicit return } fn complex_computation(x: i32) -> i32 { // The entire function body is one big expression let y = { let temp = x * 2; temp + 5 }; let z = if y > 10 { y * 3 } else { y * 2 }; match z { 0..=20 => z + 10, 21..=40 => z * 2, _ => z - 10, } // This is the return value (no semicolon) } fn main() { println!("add_v1: {}", add_v1(5, 3)); println!("add_v2: {}", add_v2(5, 3)); println!("complex: {}", complex_computation(7)); } 5. Function Overloading (Not Supported)
// Rust doesn't support traditional function overloading // But you can achieve similar results with traits or generics // Using generics instead of overloading fn print_value<T: std::fmt::Display>(value: T) { println!("Value: {}", value); } // Using different function names fn add_i32(a: i32, b: i32) -> i32 { a + b } fn add_f64(a: f64, b: f64) -> f64 { a + b } // Using enum for different types enum Input { Integer(i32), Float(f64), Text(String), } fn process_input(input: Input) -> String { match input { Input::Integer(i) => format!("Integer: {}", i), Input::Float(f) => format!("Float: {}", f), Input::Text(s) => format!("Text: {}", s), } } fn main() { print_value(42); print_value(3.14); print_value("hello"); println!("i32 sum: {}", add_i32(5, 3)); println!("f64 sum: {}", add_f64(5.5, 3.3)); println!("{}", process_input(Input::Integer(42))); println!("{}", process_input(Input::Float(3.14))); println!("{}", process_input(Input::Text("hello".to_string()))); } 6. Generic Functions
Basic Generics
// Generic function with one type parameter fn identity<T>(value: T) -> T { value } // Generic with multiple type parameters fn swap<A, B>(a: A, b: B) -> (B, A) { (b, a) } // Generic with trait bounds fn largest<T: PartialOrd + Copy>(list: &[T]) -> T { let mut largest = list[0]; for &item in list { if item > largest { largest = item; } } largest } // Generic with where clause fn print_display<T>(value: T) where T: std::fmt::Display, { println!("Display: {}", value); } fn print_debug<T>(value: T) where T: std::fmt::Debug, { println!("Debug: {:?}", value); } fn main() { let x = identity(42); let y = identity("hello"); println!("x: {}, y: {}", x, y); let swapped = swap(5, "hello"); println!("swapped: {:?}", swapped); let numbers = vec![34, 50, 25, 100, 65]; println!("Largest number: {}", largest(&numbers)); let chars = vec!['y', 'm', 'a', 'q']; println!("Largest char: {}", largest(&chars)); print_display(42); print_debug(vec![1, 2, 3]); } Advanced Generics
use std::fmt::Display; use std::ops::Add; // Generic with multiple bounds fn compare_and_print<T: PartialOrd + Display>(a: T, b: T) { if a > b { println!("{} is greater than {}", a, b); } else if a < b { println!("{} is less than {}", a, b); } else { println!("{} equals {}", a, b); } } // Generic with associated types trait Container { type Item; fn add(&mut self, item: Self::Item); fn get(&self, index: usize) -> Option<&Self::Item>; } impl<T> Container for Vec<T> { type Item = T; fn add(&mut self, item: T) { self.push(item); } fn get(&self, index: usize) -> Option<&T> { self.get(index) } } // Generic with const generics fn sum_array<const N: usize>(arr: [i32; N]) -> i32 { let mut sum = 0; for i in 0..N { sum += arr[i]; } sum } // Generic with custom trait trait Double { fn double(self) -> Self; } impl Double for i32 { fn double(self) -> Self { self * 2 } } impl Double for f64 { fn double(self) -> Self { self * 2.0 } } fn double_value<T: Double>(value: T) -> T { value.double() } fn main() { compare_and_print(10, 5); compare_and_print(3.14, 2.71); let mut vec = Vec::new(); vec.add(1); vec.add(2); println!("vec[0]: {:?}", vec.get(0)); let arr = [1, 2, 3, 4, 5]; println!("Sum of array: {}", sum_array(arr)); println!("Double i32: {}", double_value(21)); println!("Double f64: {}", double_value(3.14)); } 7. Higher-Order Functions
Functions as Parameters
// Function that takes a function as parameter fn apply_twice<F>(f: F, x: i32) -> i32 where F: Fn(i32) -> i32, { f(f(x)) } // Function that returns a function fn create_adder(x: i32) -> impl Fn(i32) -> i32 { move |y| x + y } // Using function pointers fn add_one(x: i32) -> i32 { x + 1 } fn do_twice(f: fn(i32) -> i32, arg: i32) -> i32 { f(arg) + f(arg) } // Function with different Fn traits fn call_fn<F>(f: F) where F: Fn(), { f(); } fn call_fn_mut<F>(mut f: F) where F: FnMut(), { f(); f(); } fn call_fn_once<F>(f: F) where F: FnOnce(), { f(); } fn main() { // Passing closure let result = apply_twice(|x| x * 2, 5); println!("apply_twice: {}", result); // (5*2)*2 = 20 // Using returned function let add_five = create_adder(5); println!("add_five(3): {}", add_five(3)); // Function pointer let result = do_twice(add_one, 5); println!("do_twice: {}", result); // 6 + 6 = 12 // Different Fn traits let x = 5; let print_x = || println!("x = {}", x); // Fn call_fn(print_x); let mut count = 0; let mut increment = || { count += 1; println!("count = {}", count); }; // FnMut call_fn_mut(increment); let s = String::from("hello"); let consume = move || { println!("s = {}", s); }; // FnOnce call_fn_once(consume); } Common Higher-Order Functions
fn main() { let numbers = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; // Map let squares: Vec<i32> = numbers.iter().map(|&x| x * x).collect(); println!("Squares: {:?}", squares); // Filter let evens: Vec<i32> = numbers.iter().filter(|&&x| x % 2 == 0).copied().collect(); println!("Evens: {:?}", evens); // Fold (reduce) let sum: i32 = numbers.iter().fold(0, |acc, &x| acc + x); println!("Sum: {}", sum); // Any let has_negative = numbers.iter().any(|&x| x < 0); println!("Has negative: {}", has_negative); // All let all_positive = numbers.iter().all(|&x| x > 0); println!("All positive: {}", all_positive); // Find let first_even = numbers.iter().find(|&&x| x % 2 == 0); println!("First even: {:?}", first_even); // Position let position_of_5 = numbers.iter().position(|&x| x == 5); println!("Position of 5: {:?}", position_of_5); // Chain multiple operations let result: i32 = numbers .iter() .filter(|&&x| x % 2 == 0) .map(|&x| x * x) .fold(0, |acc, x| acc + x); println!("Sum of squares of evens: {}", result); } 8. Closures
Basic Closures
fn main() { // Simple closure let add_one = |x| x + 1; println!("add_one(5): {}", add_one(5)); // Closure with type annotations let multiply = |x: i32, y: i32| -> i32 { x * y }; println!("multiply(3, 4): {}", multiply(3, 4)); // Closure capturing environment let x = 5; let add_x = |y| x + y; println!("add_x(3): {}", add_x(3)); // Closure with multiple statements let complex = |x| { let y = x * 2; let z = y + 5; z * 3 }; println!("complex(5): {}", complex(5)); // Returning closure let create_adder = |x| move |y| x + y; let add_ten = create_adder(10); println!("add_ten(7): {}", add_ten(7)); } Capture Modes
fn main() { // By reference (Fn) let s1 = String::from("hello"); let print_ref = || println!("s1: {}", s1); print_ref(); println!("Still have s1: {}", s1); // By mutable reference (FnMut) let mut s2 = String::from("hello"); let mut append_world = || { s2.push_str(", world"); println!("s2: {}", s2); }; append_world(); // append_world(); // Can call multiple times println!("s2 after: {}", s2); // By value (FnOnce) let s3 = String::from("hello"); let consume = || { let s = s3; println!("s: {}", s); }; consume(); // consume(); // Can't call twice // println!("s3: {}", s3); // s3 moved // Mixing capture modes let a = String::from("a"); let mut b = String::from("b"); let c = String::from("c"); let mixed = || { println!("a: {}", a); // by reference b.push_str("b"); // by mutable reference drop(c); // by value (FnOnce) }; // mixed(); // This would be FnOnce due to drop(c) } Closures and Move
fn main() { // Move semantics in closures let v1 = vec![1, 2, 3]; let v2 = vec![4, 5, 6]; // Without move - captures by reference let print_v1 = || { println!("v1: {:?}", v1); }; print_v1(); println!("Still have v1: {:?}", v1); // With move - captures by value let take_v2 = move || { println!("v2: {:?}", v2); }; take_v2(); // println!("v2: {:?}", v2); // Error: v2 moved // Practical use: spawning threads let data = vec![1, 2, 3, 4, 5]; std::thread::spawn(move || { println!("Thread data: {:?}", data); }).join().unwrap(); // Can't use data here - it's moved // println!("Main data: {:?}", data); } 9. Function Pointers
Using Function Pointers
// Function pointer type fn add(a: i32, b: i32) -> i32 { a + b } fn subtract(a: i32, b: i32) -> i32 { a - b } fn multiply(a: i32, b: i32) -> i32 { a * b } // Function taking function pointer fn calculate(op: fn(i32, i32) -> i32, a: i32, b: i32) -> i32 { op(a, b) } // Function returning function pointer fn get_operation(op_name: &str) -> Option<fn(i32, i32) -> i32> { match op_name { "add" => Some(add), "sub" => Some(subtract), "mul" => Some(multiply), _ => None, } } // Array of function pointers fn main() { let ops: [fn(i32, i32) -> i32; 3] = [add, subtract, multiply]; for (i, op) in ops.iter().enumerate() { let result = op(10, 5); println!("Operation {}: {}", i, result); } println!("add(5, 3) = {}", calculate(add, 5, 3)); println!("subtract(5, 3) = {}", calculate(subtract, 5, 3)); if let Some(op) = get_operation("add") { println!("Dynamic operation: {}", op(15, 7)); } // Function pointers vs closures let closure = |a: i32, b: i32| a * b; // Closure that doesn't capture can be coerced to fn pointer let fn_ptr: fn(i32, i32) -> i32 = |a, b| a + b; println!("Closure as fn pointer: {}", fn_ptr(5, 3)); // But capturing closures cannot let x = 5; // let capturing: fn(i32) -> i32 = |a| a + x; // Error } 10. Diverging Functions
Functions that Never Return
// Diverging function (returns never type !) fn forever() -> ! { loop { println!("This will never end!"); std::thread::sleep(std::time::Duration::from_secs(1)); } } fn panic_and_die() -> ! { panic!("This function always panics"); } // Useful in match arms fn process_number(num: Option<i32>) -> i32 { match num { Some(n) => n, None => panic!("No value provided"), // panic! returns ! } } // The never type in generic contexts fn either<T>(value: Option<T>) -> T { match value { Some(val) => val, None => forever(), // forever() returns !, which coerces to any type } } fn main() { // These functions never return normally // panic_and_die(); // forever(); let result = process_number(Some(42)); println!("Result: {}", result); // The following would panic // let result = process_number(None); // Using never type with loop let x = loop { break 5; // loop with break returns value }; println!("x: {}", x); // Infinite loop with break let y = loop { if true { break 10; } }; println!("y: {}", y); } 11. Methods
Associated Functions and Methods
struct Rectangle { width: u32, height: u32, } impl Rectangle { // Associated function (doesn't take self) fn new(width: u32, height: u32) -> Rectangle { Rectangle { width, height } } // Associated function that creates a square fn square(size: u32) -> Rectangle { Rectangle { width: size, height: size, } } // Method (takes &self) fn area(&self) -> u32 { self.width * self.height } // Method (takes &mut self) fn set_width(&mut self, width: u32) { self.width = width; } // Method that takes ownership (rare) fn destroy(self) { println!("Destroying rectangle: {}x{}", self.width, self.height); } // Getter fn width(&self) -> u32 { self.width } // Method with multiple parameters fn can_hold(&self, other: &Rectangle) -> bool { self.width > other.width && self.height > other.height } } fn main() { // Call associated functions with :: let rect = Rectangle::new(30, 50); let square = Rectangle::square(20); // Call methods with . println!("Area: {}", rect.area()); println!("Can hold square: {}", rect.can_hold(&square)); let mut rect2 = Rectangle::new(10, 10); rect2.set_width(15); println!("New width: {}", rect2.width()); // rect2.destroy(); // Uncomment to move ownership // println!("{:?}", rect2); // Error: rect2 moved } Method Chaining
struct Calculator { value: i32, } impl Calculator { fn new() -> Self { Calculator { value: 0 } } fn add(mut self, x: i32) -> Self { self.value += x; self } fn subtract(mut self, x: i32) -> Self { self.value -= x; self } fn multiply(mut self, x: i32) -> Self { self.value *= x; self } fn get(self) -> i32 { self.value } } // Builder pattern struct PersonBuilder { name: Option<String>, age: Option<u32>, email: Option<String>, } impl PersonBuilder { fn new() -> Self { PersonBuilder { name: None, age: None, email: None, } } fn name(mut self, name: &str) -> Self { self.name = Some(name.to_string()); self } fn age(mut self, age: u32) -> Self { self.age = Some(age); self } fn email(mut self, email: &str) -> Self { self.email = Some(email.to_string()); self } fn build(self) -> Person { Person { name: self.name.expect("Name is required"), age: self.age.unwrap_or(0), email: self.email, } } } struct Person { name: String, age: u32, email: Option<String>, } fn main() { // Method chaining let result = Calculator::new() .add(5) .multiply(2) .subtract(3) .add(10) .get(); println!("Calculator result: {}", result); // Builder pattern let person = PersonBuilder::new() .name("Alice") .age(30) .email("[email protected]") .build(); println!("Person: {} ({} years old)", person.name, person.age); } 12. Function Attributes
Inline and Other Attributes
#![allow(dead_code)] // Inline attribute - hint to compiler to inline function #[inline] fn small_function(x: i32) -> i32 { x * 2 } // Always inline #[inline(always)] fn very_small(x: i32) -> i32 { x + 1 } // Never inline #[inline(never)] fn large_function(x: i32) -> i32 { // Complex computation let mut result = x; for i in 0..10 { result += i; } result } // Deprecated function #[deprecated(since = "1.2.0", note = "Use new_function instead")] fn old_function() { println!("This is old"); } // Conditional compilation #[cfg(target_os = "linux")] fn linux_specific() { println!("This runs only on Linux"); } #[cfg(not(target_os = "linux"))] fn linux_specific() { println!("This runs on non-Linux systems"); } // Testing attribute #[test] fn test_small_function() { assert_eq!(small_function(5), 10); } // Ignored test #[test] #[ignore] fn expensive_test() { // This test won't run by default } // Must-use attribute #[must_use] fn important_calculation() -> i32 { 42 } fn main() { println!("Small: {}", small_function(5)); println!("Very small: {}", very_small(10)); println!("Large: {}", large_function(5)); old_function(); linux_specific(); let result = important_calculation(); println!("Important: {}", result); // Warning if we don't use result } 13. Recursive Functions
Basic Recursion
// Simple recursive function fn factorial(n: u64) -> u64 { match n { 0 | 1 => 1, _ => n * factorial(n - 1), } } // Tail recursion (optimized by compiler) fn factorial_tail(n: u64, acc: u64) -> u64 { match n { 0 | 1 => acc, _ => factorial_tail(n - 1, acc * n), } } // Fibonacci fn fibonacci(n: u64) -> u64 { match n { 0 => 0, 1 => 1, _ => fibonacci(n - 1) + fibonacci(n - 2), } } // More efficient Fibonacci with memoization use std::collections::HashMap; fn fibonacci_memo(n: u64, memo: &mut HashMap<u64, u64>) -> u64 { if let Some(&result) = memo.get(&n) { return result; } let result = match n { 0 => 0, 1 => 1, _ => fibonacci_memo(n - 1, memo) + fibonacci_memo(n - 2, memo), }; memo.insert(n, result); result } // Tree traversal with recursion #[derive(Debug)] struct TreeNode<T> { value: T, left: Option<Box<TreeNode<T>>>, right: Option<Box<TreeNode<T>>>, } impl<T: std::fmt::Debug> TreeNode<T> { fn new(value: T) -> Self { TreeNode { value, left: None, right: None, } } fn insert_left(&mut self, value: T) { self.left = Some(Box::new(TreeNode::new(value))); } fn insert_right(&mut self, value: T) { self.right = Some(Box::new(TreeNode::new(value))); } fn traverse_preorder(&self) { println!("{:?}", self.value); if let Some(left) = &self.left { left.traverse_preorder(); } if let Some(right) = &self.right { right.traverse_preorder(); } } } fn main() { println!("Factorial of 5: {}", factorial(5)); println!("Tail factorial of 5: {}", factorial_tail(5, 1)); println!("Fibonacci of 10: {}", fibonacci(10)); let mut memo = HashMap::new(); println!("Memoized Fibonacci of 10: {}", fibonacci_memo(10, &mut memo)); // Tree example let mut root = TreeNode::new(1); root.insert_left(2); root.insert_right(3); if let Some(left) = &mut root.left { left.insert_left(4); left.insert_right(5); } println!("Tree traversal:"); root.traverse_preorder(); } 14. Function Safety and Best Practices
Safety Considerations
// Function with preconditions fn divide_safe(numerator: f64, denominator: f64) -> Option<f64> { if denominator == 0.0 { None } else { Some(numerator / denominator) } } // Function with invariants struct PositiveInteger(i32); impl PositiveInteger { fn new(value: i32) -> Option<Self> { if value > 0 { Some(PositiveInteger(value)) } else { None } } fn value(&self) -> i32 { self.0 } } // Unsafe function unsafe fn dangerous_operation(ptr: *const i32) -> i32 { *ptr } // Safe wrapper around unsafe code fn safe_operation(value: &i32) -> i32 { *value } // Function with explicit panics fn get_element_at_index<T>(vec: &[T], index: usize) -> &T { if index >= vec.len() { panic!("Index out of bounds: {} >= {}", index, vec.len()); } &vec[index] } fn main() { // Safe division match divide_safe(10.0, 2.0) { Some(result) => println!("Result: {}", result), None => println!("Division by zero"), } // Positive integer if let Some(num) = PositiveInteger::new(5) { println!("Positive integer: {}", num.value()); } // Unsafe requires unsafe block let x = 42; let ptr = &x as *const i32; unsafe { println!("Dangerous: {}", dangerous_operation(ptr)); } // Panic on invalid index let vec = vec![1, 2, 3]; // println!("{}", get_element_at_index(&vec, 5)); // Panics println!("{}", get_element_at_index(&vec, 1)); // Works } Function Design Best Practices
// 1. Keep functions focused (single responsibility) fn process_user_data(data: &str) -> Result<ProcessedData, Error> { // Only process, don't validate or save Ok(ProcessedData::new(data)) } // 2. Use meaningful names // Bad fn f(x: i32) -> i32 { x * 2 } // Good fn double(value: i32) -> i32 { value * 2 } // 3. Limit number of parameters // Bad fn create_user_bad(name: &str, age: i32, email: &str, phone: &str, address: &str, active: bool) {} // Good - use struct struct UserConfig { name: String, age: i32, email: String, phone: Option<String>, address: Option<String>, active: bool, } fn create_user_good(config: UserConfig) {} // 4. Return meaningful errors fn read_file_safe(path: &str) -> Result<String, std::io::Error> { std::fs::read_to_string(path) } // 5. Document functions /// Calculates the area of a rectangle. /// /// # Arguments /// /// * `width` - The width of the rectangle /// * `height` - The height of the rectangle /// /// # Returns /// /// The area of the rectangle as a `u32` /// /// # Examples /// /// ``` /// let area = rectangle_area(5, 10); /// assert_eq!(area, 50); /// ``` fn rectangle_area(width: u32, height: u32) -> u32 { width * height } struct ProcessedData { content: String, } impl ProcessedData { fn new(content: &str) -> Self { ProcessedData { content: content.to_string(), } } } #[derive(Debug)] struct Error; fn main() { let config = UserConfig { name: "Alice".to_string(), age: 30, email: "[email protected]".to_string(), phone: None, address: None, active: true, }; create_user_good(config); println!("Area: {}", rectangle_area(5, 10)); } Conclusion
Rust's function system provides powerful and flexible tools for organizing code:
Key Takeaways
- Function Declaration:
fnkeyword with explicit parameter and return types - Statements vs Expressions: Understanding the difference for implicit returns
- Parameters: Pattern matching, references, and borrowing
- Return Values: Single values, tuples, Result, Option, and references
- Generics: Type-parameterized functions for code reuse
- Higher-Order Functions: Functions as parameters and return values
- Closures: Anonymous functions that capture environment
- Methods: Functions associated with types
- Function Pointers: References to functions
- Diverging Functions: Functions that never return (!)
Best Practices
- Keep functions focused on a single task
- Use meaningful names that describe purpose
- Limit the number of parameters (consider structs)
- Document public functions with doc comments
- Handle errors appropriately with Result/Option
- Use generics to avoid code duplication
- Prefer expressions over statements when appropriate
- Follow Rust naming conventions (snake_case for functions)
Function Traits Summary
| Trait | Description | Called |
|---|---|---|
Fn | Immutably captures environment | Multiple times |
FnMut | Mutably captures environment | Multiple times |
FnOnce | Consumes captured variables | Once |
Rust's function system combines the best of procedural and functional programming, providing both performance and expressiveness while maintaining strong safety guarantees through the type system and borrow checker.