Functions
Write a Rust Function
Functions are fundamental building blocks in Rust, used to encapsulate reusable blocks of code. Functions are defined with fn
, followed by the function name, optional parameters between parentheses (...)
, optional ->
followed by the return type, and curly braces { ... }
for the function body. Rust uses snake_case for function names by convention:
Here are examples of functions with or without parameters or return value:
/// Function with no parameters, no return type. fn say_hello() { println!("Hello, Rust!"); } /// Function with one parameter called `name` of type `&str` (string slice). fn greet(name: &str) { println!("Hello, {}!", name); } // Function with two parameters: `a` and `b`, both of type `i32`. fn add_and_display(a: i32, b: i32) { let sum = a + b; println!("{} + {} = {}", a, b, sum); } /// Function that takes two `i32` values and returns their sum as an `i32`. fn multiply(x: i32, y: i32) -> i32 { x * y // No semicolon: this expression's value is returned. } /// Function bodies follow the same rules than blocks: fn foo(x: i32) -> i32 { let y = { let z = 3; x + z // Blocks return the value of their last expression, if there is no semi-colon. }; y // Similarly, functions return the value of their last expression, if there is no semi-colon. } /// Function that implicitly returns the unit type `()`. /// This is equivalent to `fn print_coordinates(x: i32, y: i32) -> ()`. fn print_coordinates(x: i32, y: i32) { println!("Coordinates: ({}, {})", x, y); // Note the semicolon. // No last expression needed to return `()`. } /// Function using an explicit `return` keyword (rarely needed). fn find_first_even(numbers: &[i32]) -> Option<i32> { for &num in numbers { if num % 2 == 0 { return Some(num); // Return early, breaking out of the `for` loop. } } None // Return `None` if no even number is found (last expression). } fn main() { // Call the functions above. say_hello(); greet("Rustacean"); add_and_display(5, 3); println!("2 * 3 = {}", multiply(2, 3)); println!("Foo: {}", foo(1)); print_coordinates(1, 2); println!( "First even number: {:?}", find_first_even(vec![1, 2, 3].as_slice()) ); }
Generic Functions
Functions can be generic, meaning they can operate on parameters of various types without needing to be rewritten for each type:
// This is a generic function that can take any type. // The type parameter `T` is written between < and >. // It often can be inferred from the argument passed to the function. fn generic<T>(_: T) { println!("Argument type: {}", std::any::type_name::<T>()); } /// A generic function can take multiple type parameters. /// /// This function takes two arguments of potentially different types: fn take_two<T, U>(_a: T, _b: U) { println!( "Argument types: {} {}", std::any::type_name::<T>(), std::any::type_name::<U>() ); } fn main() { // The type parameter `T` is inferred to be `char` in this case. generic('a'); // The type parameter `T` is inferred to be `i32` in this case. generic(1); // We can also explicitly specify the type parameter `char` for `generic()`. // Note the use of the 'turbofish' notation: `::<>` generic::<char>('a'); take_two(Some(3), "4"); }
Type parameters can be constrained to implement specified traits:
use std::fmt::Debug; use std::fmt::Display; /// Generic functions can accept trait bounds. /// /// This function works for any type `T` that implements the `Debug` trait. fn print_value<T: Debug>(value: T) { println!("The value is: {:?}", value); // `{:?}` can be used, because `value` implements `Debug`. } /// Type constraints can be written in a separate `where` clause for clarity. /// You can also combine multiple trait bounds with `+`. fn print_value2<T>(value: T) where T: Debug + Display, { println!("Compare {} and {:?}", value, value); } /// All type parameters have an implicit bound of `Sized`. /// The special syntax `?Sized` can be used to remove this bound if it is not /// appropriate. /// /// This function can work with types that may not have a known size at compile /// time. fn generic<T: ?Sized + Display>(t: &T) { println!("{}", t); } fn main() { print_value(1); print_value2("2"); let s = String::from("hello"); generic(&s[..]); generic(&s); }
Diverging Functions
Functions that are guaranteed never to return (e.g., they loop forever or exit the process) have the special return type !
(the "never" type).
/// This function diverges, meaning it never returns. /// It uses the `!` (Never) type to indicate this. fn foo() -> ! { panic!("This call never returns."); } // This function never returns control. #[allow(dead_code)] fn exit_program() -> ! { println!("Exiting the process..."); std::process::exit(1); } // This function loops forever. #[allow(dead_code)] fn forever() -> ! { loop { println!("and ever..."); std::thread::sleep(std::time::Duration::from_secs(1)); } } fn main() { foo(); // This will panic. }
Function Pointers
Functions are first-class citizens in Rust. This means you can treat them like any other value: assign them to variables, pass them as arguments to other functions, etc. The type of a function pointer looks like fn(i32) -> i32
.
/// Add one to the input. fn add_one(x: i32) -> i32 { x + 1 } /// This function takes a function pointer. fn do_twice(f: fn(i32) -> i32, arg: i32) -> i32 { f(arg) + f(arg) } fn main() { let func_ptr: fn(i32) -> i32 = add_one; // Assign a function pointer to a variable. println!("Calling via the pointer: {}", func_ptr(10)); // Output: 11. let answer = do_twice(add_one, 5); // Pass `add_one` as an argument. println!("The answer is: {}", answer); // Output: 12 ( (5+1) + (5+1) ) }
Related Topics
- Closures.
- Rust Patterns.