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.