8.2 Defining and Calling Functions
Rust uses the fn
keyword to define functions. A key difference from C/C++ is that Rust does not require forward declarations. You can define a function anywhere in the module (usually a .rs
file), and call it from code that appears earlier in the same file. The compiler processes the entire module before resolving calls.
8.2.1 Basic Function Definition Syntax
The general syntax for defining a function is:
fn function_name(parameter1: Type1, parameter2: Type2) -> ReturnType {
// Function body: statements and expressions
// The last expression can be the return value (if no semicolon)
}
fn
: Keyword to start a function definition.function_name
: The identifier for the function (snake_case is conventional).()
: Parentheses enclosing the parameter list. These are required even if the function takes no parameters.parameter: Type
: Inside the parentheses, each parameter consists of a name followed by a colon and its type. Parameters are separated by commas.-> ReturnType
: An optional arrow->
followed by the type of the value the function returns. If omitted, the function returns the unit type()
.{ ... }
: The function body, enclosed in curly braces.
Example:
fn main() { // Calling greet before its definition is allowed. greet("World"); let sum = add_numbers(5, 3); println!("5 + 3 = {}", sum); } // A function that takes a string slice and prints a greeting. Returns (). fn greet(name: &str) { println!("Hello, {}!", name); } // A function that takes two i32 integers and returns their sum. fn add_numbers(a: i32, b: i32) -> i32 { a + b // This expression is the return value }
Comparison with C:
In C, if you call add_numbers
before its definition, you typically need a forward declaration (prototype) like int add_numbers(int a, int b);
near the top of the file or in a header file. Rust eliminates this requirement within a module.
8.2.2 Calling Functions
To call a function, use its name followed by parentheses ()
. If the function expects arguments, provide them inside the parentheses in the correct order and with matching types.
fn print_coordinates(x: i32, y: i32) { println!("Coordinates: ({}, {})", x, y); } // A function that takes no arguments fn display_separator() { println!("--------------------"); } fn main() { print_coordinates(10, 20); // Call with arguments 10 and 20. display_separator(); // no arguments - parentheses are still required. }
- Parentheses
()
: Always required for a function call, even if the function takes no parameters, as seen withdisplay_separator()
. - Arguments: If the function defines parameters, you must provide arguments inside the parentheses. These arguments must match the number, type, and order of the parameters defined in the function signature. Multiple arguments are separated by commas (
,
), as seen withprint_coordinates(10, 20)
.
8.2.3 Ignoring Function Return Values
If a function returns a value but you don’t need it, you can simply call the function without assigning the result to a variable.
fn get_status_code() -> u16 { 200 // Represents an HTTP OK status } fn main() { get_status_code(); // The returned value 200 is discarded. }
However, some functions, particularly those returning Result<T, E>
, are often marked with the #[must_use]
attribute. If you ignore the return value of such a function, the Rust compiler will issue a warning, as ignoring it might mean overlooking a potential error or important outcome.
#[must_use = "this Result must be handled"] fn check_condition() -> Result<(), String> { // ... logic that might fail ... Ok(()) } fn main() { check_condition(); // Compiler warning: unused result which must be used // To explicitly ignore a #[must_use] value: let _ = check_condition(); // Assigning to '_' silences the warning. // or simply: // _ = check_condition(); }
It’s generally good practice to handle or explicitly ignore Result
values rather than letting them be implicitly discarded.