Introduction to Functions in Rust
Functions are fundamental building blocks in Rust, allowing you to organize code into reusable, named blocks. Rust's function system combines elements from functional and systems programming, offering powerful features like pattern matching in parameters, multiple return types, and closure support while maintaining performance and safety.
Key Concepts
- Function Declaration: Defined with
fnkeyword - Parameters: Typed inputs to functions
- Return Values: Functions always return something (even if implicitly)
- Statements vs Expressions: Understanding the distinction
- Ownership: Functions are boundaries for ownership transfer
- Generic Functions: Write code that works with multiple types
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 // Note: no semicolon means this is the return value } // Function with explicit return statement fn multiply(a: i32, b: i32) -> i32 { return a * b; // Explicit return (less idiomatic) } fn main() { greet(); greet_person("Alice"); let sum = add(5, 3); println!("Sum: {}", sum); let product = multiply(4, 2); println!("Product: {}", product); } Statements vs Expressions
// Statements are instructions that don't return a value fn statements_example() { let x = 5; // Statement let y = { let z = 10; // Statement z * 2 // Expression (returns value) }; // The entire block is an expression that returns 20 println!("y = {}", y); // if expressions let condition = true; let result = if condition { 5 } else { 6 }; // Expression println!("result = {}", result); // match expressions let number = 3; let description = match number { 1 => "one", 2 => "two", _ => "other", }; // Expression println!("description = {}", description); } fn main() { statements_example(); } Function with Multiple Statements
fn complex_calculation(x: i32, y: i32) -> i32 { // Multiple statements let sum = x + y; let product = x * y; // Some conditional logic if sum > product { println!("Sum is larger"); sum } else { println!("Product is larger or equal"); product } // This is the return value } fn main() { let result = complex_calculation(5, 3); println!("Result: {}", result); let result2 = complex_calculation(1, 10); println!("Result: {}", result2); } 2. Function Parameters
Basic Parameters
// Parameters with different types fn print_info(name: &str, age: u32, height: f64) { println!("Name: {}, Age: {}, Height: {:.2}", name, age, height); } // Multiple parameters of same type fn sum(a: i32, b: i32, c: i32) -> i32 { a + b + c } // Default values? Rust doesn't have default parameters // Instead, use Option or create multiple functions fn greet_with_title(name: &str, title: Option<&str>) { match title { Some(t) => println!("Hello, {} {}!", t, name), None => println!("Hello, {}!", name), } } fn main() { print_info("Alice", 30, 5.8); println!("Sum: {}", sum(1, 2, 3)); greet_with_title("Bob", Some("Dr.")); greet_with_title("Charlie", None); } Passing by Value vs Reference
// Pass by value (ownership moves) fn take_ownership(s: String) { println!("Took ownership of: {}", s); } // s dropped here // Pass by reference (borrowing) fn borrow_string(s: &String) { println!("Borrowed: {}", s); } // s not dropped // Pass by mutable reference fn modify_string(s: &mut String) { s.push_str(" world"); } fn main() { // By value let s1 = String::from("hello"); take_ownership(s1); // println!("{}", s1); // Error: s1 moved // By reference let s2 = String::from("hello"); borrow_string(&s2); println!("Still have: {}", s2); // OK // By mutable reference let mut s3 = String::from("hello"); modify_string(&mut s3); println!("Modified: {}", s3); } Pattern Matching in Parameters
// Destructuring tuples in parameters fn print_tuple((x, y): (i32, i32)) { println!("Tuple: ({}, {})", x, y); } // Destructuring structs struct Point { x: i32, y: i32, } fn print_point(Point { x, y }: Point) { println!("Point: ({}, {})", x, y); } // Destructuring with pattern matching fn process_option(opt: Option<i32>) { match opt { Some(x) => println!("Got: {}", x), None => println!("Got nothing"), } } fn main() { print_tuple((10, 20)); let p = Point { x: 5, y: 15 }; print_point(p); process_option(Some(42)); process_option(None); } 3. Return Values
Different Return Patterns
// Single return value fn square(x: i32) -> i32 { x * x } // Multiple return values using tuples fn divide(dividend: i32, divisor: i32) -> (i32, i32) { (dividend / divisor, dividend % divisor) } // Return Result for error handling fn safe_divide(dividend: f64, divisor: f64) -> Result<f64, String> { if divisor == 0.0 { Err(String::from("Division by zero")) } else { Ok(dividend / divisor) } } // Return Option for nullable values fn find_index(vec: &[i32], target: i32) -> Option<usize> { for (i, &val) in vec.iter().enumerate() { if val == target { return Some(i); } } None } fn main() { println!("Square: {}", square(5)); let (q, r) = divide(17, 5); println!("Quotient: {}, Remainder: {}", q, r); match safe_divide(10.0, 2.0) { Ok(result) => println!("Division: {}", result), Err(e) => println!("Error: {}", e), } let numbers = vec![1, 2, 3, 4, 5]; match find_index(&numbers, 3) { Some(i) => println!("Found at index: {}", i), None => println!("Not found"), } } Unit Type and Diverging Functions
// Unit type () - functions that don't return anything meaningful fn do_something() { println!("Doing something"); } // Implicitly returns () // Explicit unit return fn do_something_else() -> () { println!("Doing something else"); () } // Diverging functions (never return) fn panic_forever() -> ! { panic!("This function never returns"); } // Loop that never ends fn infinite_loop() -> ! { loop { println!("Running forever..."); } } // Using ! type in match fn process_optional(x: Option<i32>) -> i32 { match x { Some(val) => val, None => panic!("No value!"), // panic! returns ! } } fn main() { let result = do_something(); println!("Result: {:?}", result); // () // This would never return: // infinite_loop(); println!("Processing: {}", process_optional(Some(42))); // println!("{}", process_optional(None)); // This would panic } 4. Function Overloading and Generic Functions
Generic Functions
// Generic function with type parameter T fn identity<T>(x: T) -> T { x } // Generic with multiple type parameters fn swap<T, U>(pair: (T, U)) -> (U, T) { (pair.1, pair.0) } // Generic with trait bounds use std::fmt::Display; fn print_and_return<T: Display>(x: T) -> T { println!("Value: {}", x); x } // 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 { println!("{} is less than or equal to {}", a, b); } } fn main() { let x = identity(42); let y = identity("hello"); println!("x: {}, y: {}", x, y); let swapped = swap((1, "hello")); println!("Swapped: {:?}", swapped); let printed = print_and_return(3.14); println!("Printed and returned: {}", printed); compare_and_print(10, 5); compare_and_print("abc", "def"); } Generic Struct Methods
struct Point<T> { x: T, y: T, } impl<T> Point<T> { fn new(x: T, y: T) -> Self { Point { x, y } } fn x(&self) -> &T { &self.x } fn y(&self) -> &T { &self.y } } // Specialized implementation for f64 impl Point<f64> { fn distance_from_origin(&self) -> f64 { (self.x.powi(2) + self.y.powi(2)).sqrt() } } // Generic with different types struct MixedPoint<T, U> { x: T, y: U, } impl<T, U> MixedPoint<T, U> { fn new(x: T, y: U) -> Self { MixedPoint { x, y } } } fn main() { let int_point = Point::new(5, 10); let float_point = Point::new(3.0, 4.0); println!("int_point x: {}", int_point.x()); println!("distance: {}", float_point.distance_from_origin()); let mixed = MixedPoint::new(5, 3.14); } 5. Higher-Order Functions
Functions as Parameters
// Function that takes a function as parameter fn apply_twice<T, F>(f: F, x: T) -> T where F: Fn(T) -> T, { f(f(x)) } // Function returning a function fn make_adder(x: i32) -> impl Fn(i32) -> i32 { move |y| x + y } // Function taking different kinds of closures fn process<F>(f: F) where F: FnOnce() -> String, // FnOnce can be called once { println!("Result: {}", f()); } fn main() { let double = |x| x * 2; let quadruple = apply_twice(double, 5); println!("Quadruple: {}", quadruple); let add_five = make_adder(5); println!("Add 5 to 10: {}", add_five(10)); let name = String::from("Alice"); let greet = move || format!("Hello, {}!", name); // name moved into closure process(greet); // println!("{}", name); // Error: name moved } Common Higher-Order Functions
fn main() { let numbers = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; // map: transform each element let doubled: Vec<i32> = numbers.iter().map(|x| x * 2).collect(); println!("Doubled: {:?}", doubled); // filter: keep elements that satisfy condition let evens: Vec<&i32> = numbers.iter().filter(|&&x| x % 2 == 0).collect(); println!("Evens: {:?}", evens); // fold (reduce): accumulate values let sum: i32 = numbers.iter().fold(0, |acc, &x| acc + x); println!("Sum: {}", sum); // Chaining higher-order functions 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); // any and all let has_negative = numbers.iter().any(|&x| x < 0); let all_positive = numbers.iter().all(|&x| x > 0); println!("Has negative: {}, All positive: {}", has_negative, all_positive); // find if let Some(first_even) = numbers.iter().find(|&&x| x % 2 == 0) { println!("First even: {}", first_even); } } 6. Closures
Basic Closure Syntax
fn main() { // Basic 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(10): {}", add_x(10)); // move closure - takes ownership let s = String::from("hello"); let consume_s = move || { println!("{}", s); }; consume_s(); // println!("{}", s); // Error: s moved // Different Fn traits let fn_once = || { println!("FnOnce"); }; fn_once(); // Can be called multiple times despite name let mut counter = 0; let mut fn_mut = || { counter += 1; println!("Counter: {}", counter); }; fn_mut(); fn_mut(); } Closure Capture Modes
fn main() { // Immutable borrow let s1 = String::from("hello"); let borrow = || println!("{}", s1); borrow(); borrow(); println!("Still have s1: {}", s1); // OK // Mutable borrow let mut s2 = String::from("hello"); let mut mutate = || { s2.push_str(" world"); }; mutate(); mutate(); println!("Now s2 is: {}", s2); // Take ownership let s3 = String::from("hello"); let take = move || { let s = s3; println!("Took ownership of: {}", s); }; take(); // println!("{}", s3); // Error: s3 moved // Multiple captures in different modes let x = 5; let mut y = 10; let z = String::from("z"); let complex = || { println!("x: {}", x); // immutable borrow y += 5; // mutable borrow println!("z: {}", z); // immutable borrow }; complex(); println!("y now: {}", y); } Closures as Return Values
fn create_counter() -> impl FnMut() -> i32 { let mut count = 0; move || { count += 1; count } } fn create_adder(x: i32) -> impl Fn(i32) -> i32 { move |y| x + y } fn create_multiplier(factor: i32) -> Box<dyn Fn(i32) -> i32> { Box::new(move |x| x * factor) } fn create_processor(kind: &str) -> Box<dyn Fn(i32) -> i32> { match kind { "double" => Box::new(|x| x * 2), "square" => Box::new(|x| x * x), "negate" => Box::new(|x| -x), _ => Box::new(|x| x), } } fn main() { let mut counter = create_counter(); println!("Counter: {}", counter()); println!("Counter: {}", counter()); println!("Counter: {}", counter()); let add_five = create_adder(5); println!("Add 5 to 10: {}", add_five(10)); let multiplier = create_multiplier(3); println!("3 * 7 = {}", multiplier(7)); let processor = create_processor("square"); println!("Square of 5: {}", processor(5)); } 7. Function Pointers
Function Pointers vs Closures
fn add_one(x: i32) -> i32 { x + 1 } fn do_twice(f: fn(i32) -> i32, x: i32) -> i32 { f(f(x)) } // Function that can accept either function pointer or closure fn process<F>(f: F, x: i32) -> i32 where F: Fn(i32) -> i32, { f(x) } fn main() { // Function pointer let fn_ptr: fn(i32) -> i32 = add_one; println!("fn_ptr(5): {}", fn_ptr(5)); // Pass function pointer to function let result = do_twice(add_one, 5); println!("do_twice: {}", result); // Function pointer from closure without capture let closure_ptr: fn(i32) -> i32 = |x| x * 2; println!("closure_ptr(5): {}", closure_ptr(5)); // Process with different callables println!("process(add_one, 5): {}", process(add_one, 5)); println!("process(|x| x * 2, 5): {}", process(|x| x * 2, 5)); // Array of function pointers let operations: [fn(i32, i32) -> i32; 4] = [ |a, b| a + b, |a, b| a - b, |a, b| a * b, |a, b| a / b, ]; let a = 10; let b = 5; for (i, op) in operations.iter().enumerate() { println!("Operation {}: {}", i, op(a, b)); } } Function Pointers in Structs
struct Calculator { operation: fn(i32, i32) -> i32, } impl Calculator { fn new(op: fn(i32, i32) -> i32) -> Self { Calculator { operation: op } } fn calculate(&self, a: i32, b: i32) -> i32 { (self.operation)(a, b) } } // Command pattern using function pointers struct Command { name: String, action: fn() -> (), } impl Command { fn execute(&self) { println!("Executing command: {}", self.name); (self.action)(); } } fn hello() { println!("Hello!"); } fn goodbye() { println!("Goodbye!"); } fn main() { let add = Calculator::new(|a, b| a + b); let multiply = Calculator::new(|a, b| a * b); println!("Add: {}", add.calculate(5, 3)); println!("Multiply: {}", multiply.calculate(5, 3)); let commands = vec![ Command { name: "greet".to_string(), action: hello }, Command { name: "farewell".to_string(), action: goodbye }, Command { name: "custom".to_string(), action: || println!("Custom!") }, ]; for cmd in commands { cmd.execute(); } } 8. Method Functions
Defining Methods
struct Rectangle { width: u32, height: u32, } impl Rectangle { // Method that borrows self immutably fn area(&self) -> u32 { self.width * self.height } // Method that borrows self mutably fn set_width(&mut self, width: u32) { self.width = width; } // Method that takes ownership fn destroy(self) { println!("Destroying rectangle: {}x{}", self.width, self.height); } // Associated function (no self) fn square(size: u32) -> Rectangle { Rectangle { width: size, height: size, } } // Getter fn width(&self) -> u32 { self.width } // Setter with validation fn set_height(&mut self, height: u32) -> Result<(), String> { if height == 0 { Err("Height must be positive".to_string()) } else { self.height = height; Ok(()) } } // Method with multiple parameters fn can_hold(&self, other: &Rectangle) -> bool { self.width > other.width && self.height > other.height } // Method that returns a reference fn dimensions(&self) -> (u32, u32) { (self.width, self.height) } } fn main() { let mut rect = Rectangle { width: 30, height: 50, }; println!("Area: {}", rect.area()); println!("Width: {}", rect.width()); rect.set_width(40); println!("New width: {}", rect.width()); if let Err(e) = rect.set_height(0) { println!("Error: {}", e); } let square = Rectangle::square(20); println!("Square area: {}", square.area()); println!("Can rect hold square? {}", rect.can_hold(&square)); // rect.destroy(); // Uncomment to take ownership // println!("{:?}", rect.width()); // Error: rect moved } Method Chaining
struct StringBuilder { content: String, } impl StringBuilder { fn new() -> Self { StringBuilder { content: String::new(), } } fn append(mut self, text: &str) -> Self { self.content.push_str(text); self } fn append_line(mut self, text: &str) -> Self { self.content.push_str(text); self.content.push('\n'); self } fn build(self) -> String { self.content } } struct Calculator2 { value: i32, } impl Calculator2 { fn new(value: i32) -> Self { Calculator2 { value } } 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 result(self) -> i32 { self.value } } fn main() { let result = StringBuilder::new() .append("Hello") .append_line(", World!") .append("This is a ") .append("chained method call") .build(); println!("{}", result); let calc = Calculator2::new(10) .add(5) .multiply(2) .subtract(3) .result(); println!("Calculation result: {}", calc); } 9. Recursive Functions
Basic Recursion
fn factorial(n: u64) -> u64 { if n <= 1 { 1 } else { n * factorial(n - 1) } } fn fibonacci(n: u64) -> u64 { match n { 0 => 0, 1 => 1, _ => fibonacci(n - 1) + fibonacci(n - 2), } } // Tail recursive factorial fn factorial_tail(n: u64, acc: u64) -> u64 { if n <= 1 { acc } else { factorial_tail(n - 1, n * acc) } } fn main() { println!("Factorial of 5: {}", factorial(5)); println!("Factorial of 5 (tail): {}", factorial_tail(5, 1)); println!("Fibonacci of 10: {}", fibonacci(10)); // Be careful with deep recursion (stack overflow) // let result = factorial(10000); // Might overflow stack } Recursive Data Structures
#[derive(Debug)] enum BinaryTree<T> { Empty, Node { value: T, left: Box<BinaryTree<T>>, right: Box<BinaryTree<T>>, }, } impl<T: PartialOrd + Clone> BinaryTree<T> { fn new() -> Self { BinaryTree::Empty } fn insert(&mut self, value: T) { match self { BinaryTree::Empty => { *self = BinaryTree::Node { value, left: Box::new(BinaryTree::Empty), right: Box::new(BinaryTree::Empty), } } BinaryTree::Node { value: v, left, right } => { if value < *v { left.insert(value); } else if value > *v { right.insert(value); } // Equal values are ignored } } } fn contains(&self, value: &T) -> bool { match self { BinaryTree::Empty => false, BinaryTree::Node { value: v, left, right } => { if value == v { true } else if value < v { left.contains(value) } else { right.contains(value) } } } } fn size(&self) -> usize { match self { BinaryTree::Empty => 0, BinaryTree::Node { left, right, .. } => 1 + left.size() + right.size(), } } } fn main() { let mut tree = BinaryTree::new(); for value in [5, 3, 7, 1, 4, 6, 8] { tree.insert(value); } println!("Tree size: {}", tree.size()); println!("Contains 4? {}", tree.contains(&4)); println!("Contains 9? {}", tree.contains(&9)); } 10. Function Attributes and Macros
Function Attributes
// Inline attribute #[inline] fn small_function(x: i32) -> i32 { x + 1 } // Always inline #[inline(always)] fn always_inline(x: i32) -> i32 { x * 2 } // Never inline #[inline(never)] fn never_inline(x: i32) -> i32 { x / 2 } // Conditional compilation #[cfg(target_os = "linux")] fn os_specific() { println!("Running on Linux"); } #[cfg(target_os = "windows")] fn os_specific() { println!("Running on Windows"); } // Deprecated function #[deprecated(since = "1.2.0", note = "Use new_function instead")] fn old_function() { println!("This is old"); } // Test function #[test] fn test_addition() { assert_eq!(2 + 2, 4); } // Benchmark function #[bench] fn bench_addition(b: &mut test::Bencher) { b.iter(|| 2 + 2); } fn main() { println!("{}", small_function(5)); println!("{}", always_inline(5)); println!("{}", never_inline(5)); os_specific(); old_function(); // Compiler will warn } Function-like Macros
// Simple macro macro_rules! say_hello { () => { println!("Hello!"); }; } // Macro with parameters macro_rules! create_function { ($func_name:ident, $input:ident, $body:expr) => { fn $func_name($input: i32) -> i32 { $body } }; } create_function!(double, x, x * 2); create_function!(square, x, x * x); // Macro with repetition macro_rules! vec_of_strings { ($($x:expr),*) => { vec![$(String::from($x)),*] }; } // Macro for logging macro_rules! log { ($level:expr, $($arg:tt)*) => { println!("[{}] {}", $level, format_args!($($arg)*)); }; } fn main() { say_hello!(); println!("Double 5: {}", double(5)); println!("Square 5: {}", square(5)); let strings = vec_of_strings!["a", "b", "c"]; println!("Strings: {:?}", strings); log!("INFO", "Application started"); log!("ERROR", "Something went wrong: {}", 404); } 11. Advanced Function Features
Variadic Functions (via macros)
// Rust doesn't have variadic functions, but macros can simulate them macro_rules! sum { ($($x:expr),*) => { { let mut total = 0; $(total += $x;)* total } }; ($($x:expr,)*) => { sum!($($x),*) }; } macro_rules! print_all { ($($arg:expr),*) => { $( print!("{} ", $arg); )* println!(); }; } fn main() { let s = sum!(1, 2, 3, 4, 5); println!("Sum: {}", s); let s = sum!(1, 2, 3); println!("Sum: {}", s); print_all!("Hello", "world", "from", "Rust"); print_all!(1, 2, 3, 4, 5); } Function with Default Parameters (using Option)
struct Config { host: String, port: u16, timeout: Option<u64>, } fn connect(host: &str, port: u16, timeout: Option<u64>) -> Result<(), String> { println!("Connecting to {}:{}", host, port); if let Some(t) = timeout { println!("With timeout: {}s", t); } Ok(()) } // Builder pattern for functions with many parameters struct ConnectionBuilder { host: String, port: u16, timeout: Option<u64>, retries: u32, ssl: bool, } impl ConnectionBuilder { fn new(host: &str) -> Self { ConnectionBuilder { host: host.to_string(), port: 80, timeout: None, retries: 3, ssl: false, } } fn port(mut self, port: u16) -> Self { self.port = port; self } fn timeout(mut self, timeout: u64) -> Self { self.timeout = Some(timeout); self } fn retries(mut self, retries: u32) -> Self { self.retries = retries; self } fn ssl(mut self, ssl: bool) -> Self { self.ssl = ssl; self } fn connect(self) -> Result<(), String> { println!("Connecting to {}:{}", self.host, self.port); println!("SSL: {}, Retries: {}", self.ssl, self.retries); if let Some(t) = self.timeout { println!("Timeout: {}s", t); } Ok(()) } } fn main() { // Using Option for default parameters connect("localhost", 8080, None).unwrap(); connect("localhost", 8080, Some(30)).unwrap(); // Using builder pattern ConnectionBuilder::new("example.com") .port(443) .ssl(true) .timeout(60) .retries(5) .connect() .unwrap(); } Function with Variable Number of Arguments (using slices)
fn sum_slice(numbers: &[i32]) -> i32 { numbers.iter().sum() } fn concatenate_strings(strings: &[&str]) -> String { let mut result = String::new(); for s in strings { result.push_str(s); } result } // Using IntoIterator for flexibility fn sum_iterable<I>(numbers: I) -> i32 where I: IntoIterator<Item = i32>, { numbers.into_iter().sum() } fn main() { let numbers = [1, 2, 3, 4, 5]; println!("Sum: {}", sum_slice(&numbers)); println!("Sum: {}", sum_slice(&[1, 2, 3])); println!("Concatenated: {}", concatenate_strings(&["Hello", " ", "World"])); // Using IntoIterator println!("Sum from array: {}", sum_iterable(vec![1, 2, 3])); println!("Sum from range: {}", sum_iterable(1..=5)); } 12. Error Handling in Functions
Result and Option Return Types
use std::num::ParseIntError; fn parse_number(s: &str) -> Result<i32, ParseIntError> { s.parse() } fn divide_safe(a: f64, b: f64) -> Option<f64> { if b == 0.0 { None } else { Some(a / b) } } fn process_file(filename: &str) -> Result<String, std::io::Error> { std::fs::read_to_string(filename) } // Using ? operator fn read_and_parse(filename: &str) -> Result<i32, Box<dyn std::error::Error>> { let content = std::fs::read_to_string(filename)?; let number = content.trim().parse::<i32>()?; Ok(number) } // Custom error type #[derive(Debug)] enum AppError { Io(std::io::Error), Parse(ParseIntError), InvalidData(String), } impl std::fmt::Display for AppError { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { match self { AppError::Io(e) => write!(f, "IO error: {}", e), AppError::Parse(e) => write!(f, "Parse error: {}", e), AppError::InvalidData(s) => write!(f, "Invalid data: {}", s), } } } impl std::error::Error for AppError {} // Conversions from other error types impl From<std::io::Error> for AppError { fn from(error: std::io::Error) -> Self { AppError::Io(error) } } impl From<ParseIntError> for AppError { fn from(error: ParseIntError) -> Self { AppError::Parse(error) } } fn process_with_custom_error(filename: &str) -> Result<i32, AppError> { let content = std::fs::read_to_string(filename)?; // Converts io::Error to AppError let number = content.trim().parse::<i32>()?; // Converts ParseIntError to AppError if number < 0 { return Err(AppError::InvalidData("Number must be positive".to_string())); } Ok(number) } fn main() { match parse_number("42") { Ok(n) => println!("Parsed: {}", n), Err(e) => println!("Error: {}", e), } match divide_safe(10.0, 2.0) { Some(r) => println!("Division: {}", r), None => println!("Division by zero"), } // Using ? operator in main with Box<dyn Error> fn inner_main() -> Result<(), Box<dyn std::error::Error>> { let num = read_and_parse("number.txt")?; println!("Number: {}", num); Ok(()) } if let Err(e) = inner_main() { println!("Error: {}", e); } // Using custom error match process_with_custom_error("number.txt") { Ok(n) => println!("Processed: {}", n), Err(e) => println!("App error: {}", e), } } Conclusion
Functions in Rust provide a powerful and flexible way to organize code:
Key Takeaways
- Function Declaration: Use
fnkeyword with explicit parameter and return types - Statements vs Expressions: Understand the difference for return values
- Ownership: Functions are ownership boundaries with move semantics
- Generics: Write type-safe code that works with multiple types
- Closures: Anonymous functions that capture environment
- Higher-Order Functions: Functions that take or return functions
- Methods: Functions associated with types
- Attributes: Control compilation and behavior
- Error Handling: Return
ResultandOptionfor fallible operations
Best Practices
- Keep functions focused: Each function should do one thing well
- Use descriptive names: Function names should describe what they do
- Prefer expressions over statements: More idiomatic Rust
- Document public functions: Use doc comments (
///) - Handle errors appropriately: Use
Resultfor recoverable errors - Use generics judiciously: Don't over-abstract
- Prefer immutability: Default to immutable parameters
- Use type aliases: For complex function signatures
Common Patterns
- Builder Pattern: For functions with many parameters
- Strategy Pattern: Using function pointers/closures
- Command Pattern: Encapsulating operations
- Callback Pattern: For asynchronous operations
- Pipeline Pattern: Chaining function calls
Functions are the primary way to structure Rust code, and mastering them is essential for writing clean, maintainable, and efficient Rust programs.