Functions
Rust is a systems programming language focused on speed, memory safety, and fearless concurrency — and functions are how you package logic into reusable, named building blocks.
Learn Functions in our free Rust course — a beginner-friendly interactive lesson with worked examples, a practice exercise and a quick reference.
Part of the free Rust course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
In this lesson you'll define functions with fn , pass typed parameters, return values, and understand Rust's distinction between statements and expressions.
What You'll Learn in This Lesson
1️⃣ Defining and Calling Functions
You define a function with the fn keyword, a name, a parenthesised list of typed parameters, and an optional -> Type for the return value. Every parameter must have a type annotation. To return a value, make the last line an expression with no semicolon .
Notice add ends with a + b and no semicolon — that expression is the return value. The function greet has no -> , so it returns nothing and exists only to print.
2️⃣ Statements, Expressions, and Early Returns
This is the idea that trips up newcomers: in Rust a block { } is itself an expression that evaluates to its last value. That means if blocks and even nested blocks can produce values. The return keyword still exists for leaving a function early.
The block assigned to y evaluated to 9 because inner * inner had no semicolon. In describe , the early return handles the even case, and the final if expression provides the rest.
Your turn. Fill in the blanks marked ___ , then run it.
Mini-Challenge: A Rectangle Toolkit
Write two functions yourself and call them from main . Run it with cargo run and check the output.
Common Errors (and the fix)
- "mismatched types: expected , found " — You put a semicolon after the final expression, turning the return value into nothing. Remove the trailing semicolon.
- "expected type, found ..." — A parameter is missing its type. Every parameter needs one: fn f(x: i32) .
- Forgetting the return type — If a function produces a value, declare it: fn f() -> i32 . Without -> Rust expects no return value.
- Calling before all args are given — Rust has no default arguments. Pass every parameter the signature lists.
Pro Tips
- 💡 Prefer the final expression: ending a function with an unsemicoloned expression is the idiomatic way to return.
- 💡 Functions can be declared in any order: Rust doesn't care if main calls a function defined below it.
- 💡 Keep functions small: a function that does one clear thing is easier to test and reuse.
📋 Quick Reference — Functions
Goal
Rust Syntax
Define a function
fn name(x: i32) { }
Return a value
fn f() -> i32 { 5 }
Call a function
let r = f();
Early return
return value;
Returns nothing
fn f() { }
Frequently Asked Questions
🎉 Lesson Complete!
- ✅ Define functions with fn name(params) -> Type
- ✅ Every parameter needs an explicit type
- ✅ The last expression with no semicolon is the return value
- ✅ Blocks { } are expressions; return exits early
- ✅ Next lesson: Control Flow — if/else and the loop, while, and for loops
Practice quiz
Which keyword defines a function in Rust?
- func
- def
- fn
- function
Answer: fn. Functions are defined with the fn keyword, e.g. fn add(a: i32, b: i32) -> i32.
What declares a function's return type?
- An arrow -> after the parentheses
- A colon before the body
- The returns keyword
- A => symbol
Answer: An arrow -> after the parentheses. The -> after the parameter list declares the return type, e.g. fn f() -> i32.
What becomes a function's return value?
- The first line
- Any line with return only
- The function name
- The last expression with no semicolon
Answer: The last expression with no semicolon. The last expression in the body, with no trailing semicolon, is the return value.
What happens if you add a semicolon after the final expression a + b?
- Nothing changes
- It becomes a statement evaluating to (), often causing a type mismatch
- It returns twice
- It panics
Answer: It becomes a statement evaluating to (), often causing a type mismatch. A trailing semicolon turns the expression into a statement returning (), so a fn meant to return i32 won't compile.
Must every function parameter have an explicit type?
- Yes, every parameter must be annotated
- No, types are inferred
- Only the first one
- Only for return values
Answer: Yes, every parameter must be annotated. Unlike local variables, function parameters always require explicit type annotations.
What does a function with no -> in its signature return?
- i32
- null
- The unit type ()
- An error
Answer: The unit type (). Without ->, a function returns the unit type (), meaning no meaningful value.
In Rust, a block { } is...
- only a grouping of statements
- an expression that evaluates to its last value
- always empty
- the same as a loop
Answer: an expression that evaluates to its last value. A block is an expression; its value is the last expression inside it (with no semicolon).
What is the value of let y = { let inner = 3; inner * inner };?
- 3
- 6
- ()
- 9
Answer: 9. The block evaluates to inner * inner = 9 because that line has no semicolon.
When should you use the return keyword?
- Always, at the end
- For an early exit from the middle of a function
- Never
- Only in main
Answer: For an early exit from the middle of a function. Use return to exit early; the idiomatic happy-path result is a final expression with no semicolon.
Does Rust support default argument values for functions?
- Yes
- Only for the last parameter
- No — you must pass every parameter the signature lists
- Only with a macro
Answer: No — you must pass every parameter the signature lists. Rust has no default arguments; every parameter in the signature must be provided at the call site.
Continue this course
- Previous: Data Types
- Next: Control Flow