Using Functions in Rust
Functions are reusable blocks of code that help divide a program into smaller, focused pieces. Rust functions can accept typed parameters, return values, borrow data, and work with functions or closures as inputs.
What Is a Function in Rust?
Functions are declared with the fn keyword, followed by the function name, parameters, an optional return type, and a body:
fn function_name(parameters) {
// function body
}Define and Call a Function
fn main() {
greet();
}
fn greet() {
println!("Hello, Rustacean!");
}Rust does not require a function to be defined before the call site in the source file, as long as it is visible in the current module.
Function Parameters
Parameter types are written explicitly:
fn main() {
add(5, 10);
}
fn add(a: i32, b: i32) {
let sum = a + b;
println!("Sum: {}", sum);
}Here a and b are both i32 values.
Return Values
Write the return type after ->. Rust commonly returns the final expression of a function without a semicolon:
fn main() {
let result = multiply(4, 5);
println!("Product: {}", result);
}
fn multiply(x: i32, y: i32) -> i32 {
x * y
}You can also use an explicit return, which is especially useful for early exits.
Return Multiple Values with a Tuple
A tuple can package several return values:
fn main() {
let (sum, product) = calculate(3, 7);
println!("Sum: {}, Product: {}", sum, product);
}
fn calculate(a: i32, b: i32) -> (i32, i32) {
(a + b, a * b)
}For larger or domain-specific return values, a struct may communicate intent better than a tuple.
Borrow Values Instead of Moving Them
Pass a reference when a function only needs temporary access:
fn main() {
let name = String::from("Alice");
greet_name(&name);
println!("Name after the function call: {}", name);
}
fn greet_name(name: &str) {
println!("Hello, {}!", name);
}Using &str makes the function accept both borrowed String values and string literals.
Functions and Closures
Functions can accept other callable values. If the API only needs a plain function pointer, use fn:
fn apply(a: i32, b: i32, func: fn(i32, i32) -> i32) -> i32 {
func(a, b)
}
fn main() {
let result = apply(3, 4, |x, y| x + y);
println!("Result: {}", result);
}For more flexible higher-order APIs, generic bounds such as Fn, FnMut, or FnOnce allow closures that capture values from their environment.
Best Practices
- Keep functions focused on one clear responsibility.
- Choose names that describe intent rather than implementation details.
- Prefer a struct or configuration type when a long parameter list represents one concept.
- Return
ResultorOptionwhen failure or absence is part of normal control flow. - Use references and slices when a function does not need ownership.
- Add Rustdoc comments (
///) to public APIs when their purpose or contract is not obvious from the signature.
Conclusion
Functions are the basic building blocks of modular Rust programs. Typed parameters, expression-based returns, borrowing, tuples, and closure traits give you a broad set of tools for designing functions that are clear about data ownership and behavior.