Skip to Content
Functions

Functions in Rust

Functions are fundamental to Rust. You have already seen one in every example — the main function. If you have worked with C or C++, you will recognise it: main is the entry point of every Rust program.

Functions in Rust are declared using the fn keyword. Rust uses snake_case as the conventional style for both function names and variable names.


Anatomy of a Rust Function


Naming Rules

Rust enforces specific rules for function names. The table below shows what is allowed and what is not:

RuleValid ExampleInvalid Example
Must start with a letter (a–z, A–Z) or underscore _add, _hidden2add, -start
Can contain letters, digits, and underscorescalculate2_sum, _helper123add-sum, sum!, my func
Cannot be a Rust keywordprocess_datafn, let, struct
Should use snake_case (idiomatic Rust)calculate_sum, print_messagecalculateSum, CalculateSum
Leading underscore suppresses unused-variable warnings_internal_helper
No spaces or special symbolssum_numberssum numbers, sum$

The Rust compiler will warn you if a function name uses camelCase instead of snake_case. While the program still compiles, following the convention keeps your code consistent with the rest of the ecosystem.


Defining and Calling Functions

A function is declared with the fn keyword, followed by the function name, a pair of parentheses, and a body enclosed in curly braces. Rust does not require functions to be declared before they are used — the compiler will find them anywhere within the same scope.

src/main.rs
fn main() { println!("Hello, world!"); greet(); } fn greet() { println!("Hello from a function."); }
cargo run Compiling rust_app v0.1.0 Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.91s Running `target/debug/rust_app` Hello, world! Hello from a function.

The output appears in the order the calls appear in main. println!("Hello, world!") runs first, then greet() runs next.


Parameters

A parameter is a variable in the function’s definition. When you call the function and supply a value, that value is called an argument. Technically the two words are distinct: parameters live in the definition, arguments live at the call site.

src/main.rs
fn add(a: i32, b: i32) -> i32 { // a and b are parameters a + b } fn main() { let result = add(5, 3); // 5 and 3 are arguments println!("Sum: {}", result); }
Sum: 8

Type Annotations Are Required

In a function signature, you must annotate the type of every parameter. This is a deliberate design choice: because types are explicit in function boundaries, the compiler never has to guess and can catch mistakes early with precise error messages.

src/main.rs
fn main() { print_price(99.99, "Dollar"); } fn print_price(price: f64, currency: &str) { println!("Price: {price} {currency}"); }
Price: 99.99 Dollar
ParameterTypeMeaning
pricef6464-bit floating-point number
currency&strA string slice — a borrowed reference to text

Statements and Expressions

Every line inside a Rust function is either a statement or an expression. Rust keeps this distinction strict — understanding it is key to writing correct functions.

StatementExpression
Produces a value?NoYes
Ends with ;?YesNo (adding ; turns it into a statement)
Can be assigned to a variable?NoYes
Examplelet x = 5;5 + 3, { price + shipping }

You Cannot Assign a Statement to a Variable

Because statements do not return a value, you cannot assign one to another variable:

src/main.rs
fn main() { let x = (let apples = 5); // error! }
error: expected expression, found `let` statement --> src/main.rs:2:14 | 2 | let x = (let apples = 5); | ^^^ | = note: only supported directly in conditions of `if` and `while` expressions

In languages like C or Ruby, x = y = 6 works because assignment returns a value. In Rust, let is a statement — there is nothing for x to hold.

Blocks Are Expressions

A block — code enclosed in { } — is itself an expression. Its value is whatever the last line inside it evaluates to (without a semicolon):

src/main.rs
fn main() { let total = { let price = 100; let shipping = 20; price + shipping // no semicolon → this is the value of the block }; println!("Total: {total}"); // Total: 120 }
LineTypeNote
let price = 100;StatementBinds 100 to price
let shipping = 20;StatementBinds 20 to shipping
price + shippingExpressionEvaluates to 120 — the block’s return value
let total = { ... };StatementBinds 120 to total

If you add a semicolon after price + shipping, it becomes a statement. The block then returns () (the unit type), and the compiler will raise a type mismatch error.


Return Values

A Rust function returns the value of its final expression. You declare the expected return type using -> in the function signature. No return keyword is needed for the common case.

src/main.rs
fn five() -> i32 { 5 // no semicolon — this is the return value } fn main() { let x = five(); // x = 5 println!("{x}"); }

The -> i32 in the signature is a promise: “this function will hand back a 32-bit integer.” The bare 5 at the end fulfils that promise.

Implicit vs Explicit Return

StyleWhen to useExample
Implicit (last expression)Normal case — the function completes naturallyx + 1 as the final line
Explicit (return)Early exit — bail out before reaching the endreturn n; inside an if block
src/main.rs
fn plus_one(x: i32) -> i32 { x + 1 // implicit return — no semicolon } fn absolute(n: i32) -> i32 { if n >= 0 { return n; // explicit early return } -n // implicit return for the negative case }

The Semicolon Trap

Adding a semicolon to the last line turns an expression into a statement, which changes what the function returns:

src/main.rs
fn plus_one(x: i32) -> i32 { x + 1; // semicolon → statement → returns (), not i32 }
error[E0308]: mismatched types --> src/main.rs:2:24 | 1 | fn plus_one(x: i32) -> i32 { | ^^^ expected `i32`, found `()`

The compiler even suggests the fix: remove the semicolon to return the value. This is one of the most common beginner mistakes in Rust.


Summary

ConceptKey Rule
DeclarationUse fn, followed by name, parameters, optional -> return type, and { body }
NamingUse snake_case for function and variable names
ParametersEvery parameter must have an explicit type annotation in the signature
StatementPerforms an action, returns nothing, ends with ;
ExpressionEvaluates to a value, has no trailing ;
Implicit returnThe last expression in the function body is the return value
Explicit returnUse the return keyword to exit a function early
Semicolon trapAdding ; to the last line makes it return () instead of the intended type

What’s Next?

Now that you understand how functions work in Rust, you are ready to explore Control Flow — where you will learn how to use if expressions, loops, and pattern matching to control the execution of your programs.