8.4 Returning Values from Functions
Functions can return values of almost any type. The return type is specified after the ->
arrow in the function signature.
8.4.1 Syntax for Returning Values
// Returns an i32 value. fn give_number() -> i32 { 42 // Implicit return of the expression's value } // Returns a new String. fn create_greeting(name: &str) -> String { let mut greeting = String::from("Hello, "); greeting.push_str(name); greeting // Implicit return of the variable 'greeting' } fn main() { let number = give_number(); let text = create_greeting("Alice"); println!("Number: {}", number); println!("Greeting: {}", text); }
8.4.2 Explicit return
vs. Implicit Return
Rust provides two ways to specify the return value of a function:
-
Implicit Return: If the last statement in a function body is an expression (without a trailing semicolon), its value is automatically returned. This is the idiomatic style in Rust for the common case.
fn multiply(a: i32, b: i32) -> i32 { a * b // No semicolon, this expression's value is returned. }
-
Explicit
return
Keyword: You can use thereturn
keyword to exit the function immediately with a specific value. This is often used for early returns, such as in error conditions or conditional logic.fn find_first_even(numbers: &[i32]) -> Option<i32> { for &num in numbers { if num % 2 == 0 { return Some(num); // Early return if an even number is found. } } None // Implicit return if the loop finishes without finding an even number. }
Important: Adding a semicolon ;
after the final expression turns it into a statement. Statements evaluate to the unit type ()
. If a function is expected to return a value (e.g., -> i32
), ending it with a statement like a * b;
will result in a type mismatch error, because the function implicitly returns ()
instead of the expected i32
.
fn multiply_buggy(a: i32, b: i32) -> i32 {
a * b; // Semicolon makes this a statement, function returns () implicitly.
// Compile Error: expected i32, found ()
}
Comparison with C: In C, you must use the return value;
statement to return a value from a function. Functions declared void
either have no return
statement or use return;
without a value. Rust’s implicit return from the final expression is a convenient shorthand not found in C.
8.4.3 Returning References (and Lifetimes)
Functions can return references (&T
or &mut T
), but this requires careful consideration of lifetimes. A returned reference must point to data that will remain valid after the function call has finished.
Typically, this means the returned reference must point to:
- Data that was passed into the function via a reference parameter.
- Data that exists outside the function (e.g., a
static
variable).
You cannot return a reference to a variable created locally inside the function, because that variable will be destroyed when the function exits, leaving the reference dangling (pointing to invalid memory). The Rust compiler prevents this with lifetime checks.
// This function takes a slice and returns a reference to its first element. // The lifetime 'a ensures the returned ref. is valid as long as the input slice is. fn get_first<'a>(slice: &'a [i32]) -> &'a i32 { &slice[0] // Returns a reference derived from the input slice. } // This function attempts to return a reference to a local variable (Compiler Error). // fn get_dangling_reference() -> &i32 { // let local_value = 10; // &local_value // Error: `local_value` does not live long enough // } fn main() { let numbers = [10, 20, 30]; let first = get_first(&numbers); // 'first' borrows from 'numbers'. println!("The first number is: {}", first); // 'first' remains valid as long as 'numbers' is in scope. // let dangling = get_dangling_reference(); // This would not compile. }
Returning mutable references (&mut T
) follows the same lifetime rules. This ability to safely return references, especially mutable ones, is a powerful feature enabled by Rust’s borrow checker, preventing common C/C++ errors like returning pointers to stack variables. Lifetimes are covered more deeply in a later chapter.